[evolution-data-server/wip/offline-cache] Copy Tristan's EBookSqlite tests and use then with EBookCache



commit e50bad9fe5306a7979aefd21f533d61c8f25c3f6
Author: Milan Crha <mcrha redhat com>
Date:   Wed Feb 1 22:34:42 2017 +0100

    Copy Tristan's EBookSqlite tests and use then with EBookCache

 src/addressbook/libedata-book/e-book-cache.c       |  242 +++++++-
 src/addressbook/libedata-book/e-book-cache.h       |    5 +
 src/libebackend/e-cache.c                          |   11 +-
 tests/libedata-book/CMakeLists.txt                 |   12 +
 tests/libedata-book/test-cache-create-cursor.c     |  125 ++++
 tests/libedata-book/test-cache-cursor-calculate.c  |  695 ++++++++++++++++++++
 .../test-cache-cursor-change-locale.c              |  102 +++
 .../test-cache-cursor-move-by-de-DE.c              |   82 +++
 .../test-cache-cursor-move-by-en-US.c              |  100 +++
 .../test-cache-cursor-move-by-fr-CA.c              |   82 +++
 .../test-cache-cursor-move-by-posix.c              |   82 +++
 tests/libedata-book/test-cache-cursor-set-sexp.c   |  151 +++++
 tests/libedata-book/test-cache-cursor-set-target.c |  225 +++++++
 tests/libedata-book/test-cache-get-contact.c       |   78 +++
 tests/libedata-book/test-cache-utils.c             |  691 +++++++++++++++++++
 tests/libedata-book/test-cache-utils.h             |  174 +++++
 16 files changed, 2829 insertions(+), 28 deletions(-)
---
diff --git a/src/addressbook/libedata-book/e-book-cache.c b/src/addressbook/libedata-book/e-book-cache.c
index 881bff8..f80f6af 100644
--- a/src/addressbook/libedata-book/e-book-cache.c
+++ b/src/addressbook/libedata-book/e-book-cache.c
@@ -129,6 +129,13 @@ enum {
        PROP_LOCALE
 };
 
+enum {
+       E164_CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
 G_DEFINE_TYPE_WITH_CODE (EBookCache, e_book_cache, E_TYPE_CACHE,
                         G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
 
@@ -266,6 +273,9 @@ column_info_new (SummaryField *field,
 
        g_return_val_if_fail (column_name != NULL, NULL);
 
+       if (field->type == E_TYPE_CONTACT_ATTR_LIST)
+               column_name = "value";
+
        if (!column_type) {
                if (field->type == G_TYPE_STRING)
                        column_type = "TEXT";
@@ -1009,7 +1019,7 @@ ebc_run_multi_insert_one (ECache *cache,
 
        normal = e_util_utf8_normalize (value);
 
-       e_cache_sqlite_stmt_append_printf (stmt, "INSERT INTO %Q (uid, %s", field->aux_table, field->dbname);
+       e_cache_sqlite_stmt_append_printf (stmt, "INSERT INTO %Q (uid, value", field->aux_table);
 
        if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
                g_string_append (stmt, ", value_" EBC_SUFFIX_REVERSE);
@@ -1237,26 +1247,10 @@ ebc_upgrade_cb (ECache *cache,
 
        g_clear_object (&contact);
 
-       if (g_hash_table_size (other_columns) > 0) {
-               gint ii;
-
-               for (ii = 0; ii < ncols; ii++) {
-                       if (!column_names[ii] ||
-                           !g_hash_table_contains (other_columns, column_names[ii]))
-                               continue;
-
-                       if (g_strcmp0 (g_hash_table_lookup (other_columns, column_names[ii]), 
column_values[ii]) == 0) {
-                               /* Do not try to store values which did not change */
-                               g_hash_table_remove (other_columns, column_names[ii]);
-                       }
-               }
-       }
-
-       if (g_hash_table_size (other_columns) > 0) {
-               *out_other_columns = other_columns;
-       } else {
-               g_hash_table_destroy (other_columns);
-       }
+       /* This will cause rewrite even when no values changed, but it's
+          necessary, because the locale changed, which can influence
+          other tables, not only the other columns. */
+       *out_other_columns = other_columns;
 
        return TRUE;
 }
@@ -3891,6 +3885,178 @@ cursor_count_position_locked (EBookCache *book_cache,
        return success;
 }
 
+typedef struct {
+       gint country_code;
+       gchar *national;
+} E164Number;
+
+static E164Number *
+ebc_e164_number_new (gint country_code,
+                    const gchar *national)
+{
+       E164Number *number = g_slice_new (E164Number);
+
+       number->country_code = country_code;
+       number->national = g_strdup (national);
+
+       return number;
+}
+
+static void
+ebc_e164_number_free (E164Number *number)
+{
+       if (number) {
+               g_free (number->national);
+               g_slice_free (E164Number, number);
+       }
+}
+
+static gint
+ebc_e164_number_find (E164Number *number_a,
+                     E164Number *number_b)
+{
+       gint ret;
+
+       ret = number_a->country_code - number_b->country_code;
+
+       if (ret == 0) {
+               ret = g_strcmp0 (
+                       number_a->national,
+                       number_b->national);
+       }
+
+       return ret;
+}
+
+static GList *
+extract_e164_attribute_params (EContact *contact)
+{
+       EVCard *vcard = E_VCARD (contact);
+       GList *extracted = NULL;
+       GList *attr_list;
+
+       for (attr_list = e_vcard_get_attributes (vcard); attr_list; attr_list = attr_list->next) {
+               EVCardAttribute *const attr = attr_list->data;
+               EVCardAttributeParam *param = NULL;
+               GList *param_list, *values, *l;
+               gchar *this_national = NULL;
+               gint this_country = 0;
+
+               /* We only attach E164 parameters to TEL attributes. */
+               if (strcmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
+                       continue;
+
+               /* Find already exisiting parameter, so that we can reuse it. */
+               for (param_list = e_vcard_attribute_get_params (attr); param_list; param_list = 
param_list->next) {
+                       if (strcmp (e_vcard_attribute_param_get_name (param_list->data), EVC_X_E164) == 0) {
+                               param = param_list->data;
+                               break;
+                       }
+               }
+
+               if (!param)
+                       continue;
+
+               values = e_vcard_attribute_param_get_values (param);
+               for (l = values; l; l = l->next) {
+                       const gchar *value = l->data;
+
+                       if (value[0] == '+')
+                               this_country = g_ascii_strtoll (&value[1], NULL, 10);
+                       else if (this_national == NULL)
+                               this_national = g_strdup (value);
+               }
+
+               if (this_national) {
+                       E164Number *number;
+
+                       number = ebc_e164_number_new (this_country, this_national);
+                       extracted = g_list_prepend (extracted, number);
+               }
+
+               g_free (this_national);
+
+               /* Clear the values, we'll insert new ones */
+               e_vcard_attribute_param_remove_values (param);
+               e_vcard_attribute_remove_param (attr, EVC_X_E164);
+       }
+
+       return extracted;
+}
+
+static gboolean
+update_e164_attribute_params (EBookCache *book_cache,
+                             EContact *contact,
+                             const gchar *default_region)
+{
+       GList *original_numbers = NULL;
+       GList *attr_list;
+       gboolean changed = FALSE;
+       gint n_numbers = 0;
+       EVCard *vcard = E_VCARD (contact);
+
+       original_numbers = extract_e164_attribute_params (contact);
+
+       for (attr_list = e_vcard_get_attributes (vcard); attr_list; attr_list = attr_list->next) {
+               EVCardAttribute *const attr = attr_list->data;
+               EVCardAttributeParam *param = NULL;
+               const gchar *original_number = NULL;
+               gchar *country_string;
+               GList *values;
+               E164Number number = { 0, NULL };
+
+               /* We only attach E164 parameters to TEL attributes. */
+               if (strcmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
+                       continue;
+
+               /* Fetch the TEL value */
+               values = e_vcard_attribute_get_values (attr);
+
+               /* Compute E164 number based on the TEL value */
+               if (values && values->data) {
+                       original_number = (const gchar *) values->data;
+                       number.national = convert_phone (original_number, book_cache->priv->region_code, 
&(number.country_code));
+               }
+
+               if (number.national == NULL)
+                       continue;
+
+               /* Count how many we successfully parsed in this region code */
+               n_numbers++;
+
+               /* Check if we have a differing e164 number, if there is no match
+                * in the old existing values then the vcard changed
+                */
+               if (!g_list_find_custom (original_numbers, &number, (GCompareFunc) ebc_e164_number_find))
+                       changed = TRUE;
+
+               if (number.country_code != 0)
+                       country_string = g_strdup_printf ("+%d", number.country_code);
+               else
+                       country_string = g_strdup ("");
+
+               param = e_vcard_attribute_param_new (EVC_X_E164);
+               e_vcard_attribute_add_param (attr, param);
+
+               /* Assign the parameter values. It seems odd that we revert
+                * the order of NN and CC, but at least EVCard's parser doesn't
+                * permit an empty first param value. Which of course could be
+                * fixed - in order to create a nice potential IOP problem with
+                ** other vCard parsers. */
+               e_vcard_attribute_param_add_values (param, number.national, country_string, NULL);
+
+               g_free (number.national);
+               g_free (country_string);
+       }
+
+       if (!changed && n_numbers != g_list_length (original_numbers))
+               changed = TRUE;
+
+       g_list_free_full (original_numbers, (GDestroyNotify) ebc_e164_number_free);
+
+       return changed;
+}
+
 static gboolean
 e_book_cache_get_string (ECache *cache,
                         gint ncols,
@@ -5580,16 +5746,38 @@ e_book_cache_put_locked (ECache *cache,
                         GCancellable *cancellable,
                         GError **error)
 {
+       EBookCache *book_cache;
+       EContact *contact;
+       gchar *updated_vcard = NULL;
+       gboolean e164_changed;
        gboolean success;
 
        g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
        g_return_val_if_fail (E_CACHE_CLASS (e_book_cache_parent_class)->put_locked != NULL, FALSE);
 
+       book_cache = E_BOOK_CACHE (cache);
+
+       contact = e_contact_new_from_vcard_with_uid (object, uid);
+
+       /* Update E.164 parameters in vcard if needed */
+       e164_changed = update_e164_attribute_params (book_cache, contact, book_cache->priv->region_code);
+
+       if (e164_changed) {
+               updated_vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+               object = updated_vcard;
+       }
+
        success = E_CACHE_CLASS (e_book_cache_parent_class)->put_locked (cache, uid, revision, object, 
other_columns, offline_state,
                is_replace, cancellable, error);
 
        success = success && ebc_update_aux_tables (cache, uid, revision, object, cancellable, error);
 
+       if (success && e164_changed)
+               g_signal_emit (book_cache, signals[E164_CHANGED], 0, contact, is_replace);
+
+       g_clear_object (&contact);
+       g_free (updated_vcard);
+
        return success;
 }
 
@@ -5693,6 +5881,18 @@ e_book_cache_class_init (EBookCacheClass *class)
                        NULL,
                        G_PARAM_READABLE |
                        G_PARAM_STATIC_STRINGS));
+
+       signals[E164_CHANGED] = g_signal_new (
+               "e164-changed",
+               G_OBJECT_CLASS_TYPE (class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (EBookCacheClass, e164_changed),
+               NULL,
+               NULL,
+               g_cclosure_marshal_generic,
+               G_TYPE_NONE, 2,
+               E_TYPE_CONTACT,
+               G_TYPE_BOOLEAN);
 }
 
 static void
diff --git a/src/addressbook/libedata-book/e-book-cache.h b/src/addressbook/libedata-book/e-book-cache.h
index c8ad69c..6fcfdb4 100644
--- a/src/addressbook/libedata-book/e-book-cache.h
+++ b/src/addressbook/libedata-book/e-book-cache.h
@@ -112,6 +112,11 @@ struct _EBookCacheClass {
        /*< private >*/
        ECacheClass parent_class;
 
+       /* Signals */
+       void    (* e164_changed)        (EBookCache *book_cache,
+                                        EContact *contact,
+                                        gboolean is_replace);
+
        /* Padding for future expansion */
        gpointer reserved[10];
 };
diff --git a/src/libebackend/e-cache.c b/src/libebackend/e-cache.c
index 7fe3762..e5d47f0 100644
--- a/src/libebackend/e-cache.c
+++ b/src/libebackend/e-cache.c
@@ -54,7 +54,7 @@
 #define E_CACHE_CANCEL_BATCH_SIZE      200
 
 /* How many rows to read when e_cache_foreach_update() */
-#define E_CACHE_UPDATE_BATCH_SIZE      200
+#define E_CACHE_UPDATE_BATCH_SIZE      100
 
 struct _ECachePrivate {
        gchar *filename;
@@ -999,7 +999,6 @@ e_cache_put_locked (ECache *cache,
 
        g_return_val_if_fail (E_IS_CACHE (cache), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
-       g_return_val_if_fail (revision != NULL, FALSE);
        g_return_val_if_fail (object != NULL, FALSE);
 
        if (!other_columns) {
@@ -1034,7 +1033,7 @@ e_cache_put_locked (ECache *cache,
  * e_cache_put:
  * @cache: an #ECache
  * @uid: a unique identifier of an object
- * @revision: a revision of the object
+ * @revision: (nullable): a revision of the object
  * @object: the object itself
  * @other_columns: (nullable) (element-type utf8 utf8): what other columns to set; can be %NULL
  * @cancellable: optional #GCancellable object, or %NULL
@@ -1063,7 +1062,6 @@ e_cache_put (ECache *cache,
 
        g_return_val_if_fail (E_IS_CACHE (cache), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
-       g_return_val_if_fail (revision != NULL, FALSE);
        g_return_val_if_fail (object != NULL, FALSE);
 
        g_rec_mutex_lock (&cache->priv->lock);
@@ -2336,7 +2334,7 @@ e_cache_sqlite_stmt_append_printf (GString *stmt,
 
        g_string_append (stmt, tmp_stmt);
 
-       g_free (tmp_stmt);
+       sqlite3_free (tmp_stmt);
 }
 
 /**
@@ -2444,7 +2442,6 @@ e_cache_put_locked_default (ECache *cache,
 
        g_return_val_if_fail (E_IS_CACHE (cache), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
-       g_return_val_if_fail (revision != NULL, FALSE);
        g_return_val_if_fail (object != NULL, FALSE);
 
        statement = g_string_sized_new (255);
@@ -2485,7 +2482,7 @@ e_cache_put_locked_default (ECache *cache,
 
        g_string_append (statement, ") VALUES (");
 
-       e_cache_sqlite_stmt_append_printf (statement, "%Q,%Q,%Q,%d", uid, revision, object, offline_state);
+       e_cache_sqlite_stmt_append_printf (statement, "%Q,%Q,%Q,%d", uid, revision ? revision : "", object, 
offline_state);
 
        if (other_values)
                g_string_append (statement, other_values->str);
diff --git a/tests/libedata-book/CMakeLists.txt b/tests/libedata-book/CMakeLists.txt
index 347196d..d8677d2 100644
--- a/tests/libedata-book/CMakeLists.txt
+++ b/tests/libedata-book/CMakeLists.txt
@@ -37,6 +37,8 @@ set(extra_ldflags
 set(SOURCES
        data-test-utils.c
        data-test-utils.h
+       test-cache-utils.c
+       test-cache-utils.h
 )
 
 add_library(data-test-utils STATIC
@@ -94,6 +96,16 @@ set(extra_defines)
 # This is because each migrated test changes the
 # locale and reloads the same addressbook of the previous test.
 set(TESTS
+       test-cache-get-contact
+       test-cache-create-cursor
+       test-cache-cursor-move-by-posix
+       test-cache-cursor-move-by-en-US
+       test-cache-cursor-move-by-fr-CA
+       test-cache-cursor-move-by-de-DE
+       test-cache-cursor-set-target
+       test-cache-cursor-calculate
+       test-cache-cursor-set-sexp
+       test-cache-cursor-change-locale
        test-sqlite-get-contact
        test-sqlite-create-cursor
        test-sqlite-cursor-move-by-posix
diff --git a/tests/libedata-book/test-cache-create-cursor.c b/tests/libedata-book/test-cache-create-cursor.c
new file mode 100644
index 0000000..9ef9d15
--- /dev/null
+++ b/tests/libedata-book/test-cache-create-cursor.c
@@ -0,0 +1,125 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+static TCUClosure closure = { NULL };
+
+static void
+test_create_cursor_empty_query (TCUFixture *fixture,
+                               gconstpointer user_data)
+{
+       EBookCacheCursor *cursor;
+       EContactField sort_fields[] = { E_CONTACT_FAMILY_NAME, E_CONTACT_GIVEN_NAME };
+       EBookCursorSortType sort_types[] = { E_BOOK_CURSOR_SORT_ASCENDING, E_BOOK_CURSOR_SORT_ASCENDING };
+       GError *error = NULL;
+
+       cursor = e_book_cache_cursor_new (
+               fixture->book_cache, NULL,
+               sort_fields, sort_types, 2, &error);
+
+       g_assert (cursor != NULL);
+       e_book_cache_cursor_free (fixture->book_cache, cursor);
+}
+
+static void
+test_create_cursor_valid_query (TCUFixture *fixture,
+                               gconstpointer user_data)
+{
+       EBookCacheCursor *cursor;
+       EContactField sort_fields[] = { E_CONTACT_FAMILY_NAME, E_CONTACT_GIVEN_NAME };
+       EBookCursorSortType sort_types[] = { E_BOOK_CURSOR_SORT_ASCENDING, E_BOOK_CURSOR_SORT_ASCENDING };
+       EBookQuery *query;
+       gchar *sexp;
+       GError *error = NULL;
+
+       query = e_book_query_field_test (E_CONTACT_FULL_NAME, E_BOOK_QUERY_IS, "James Brown");
+       sexp = e_book_query_to_string (query);
+
+       cursor = e_book_cache_cursor_new (
+               fixture->book_cache, sexp,
+               sort_fields, sort_types, 2, &error);
+
+       g_assert (cursor != NULL);
+       e_book_cache_cursor_free (fixture->book_cache, cursor);
+       g_free (sexp);
+       e_book_query_unref (query);
+}
+
+static void
+test_create_cursor_invalid_sort (TCUFixture *fixture,
+                                gconstpointer user_data)
+{
+       EBookCacheCursor *cursor;
+       EContactField sort_fields[] = { E_CONTACT_TEL };
+       EBookCursorSortType sort_types[] = { E_BOOK_CURSOR_SORT_ASCENDING };
+       GError *error = NULL;
+
+       cursor = e_book_cache_cursor_new (
+               fixture->book_cache, NULL,
+               sort_fields, sort_types, 1, &error);
+
+       g_assert (cursor == NULL);
+       g_assert (error);
+       g_assert (g_error_matches (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY));
+}
+
+static void
+test_create_cursor_missing_sort (TCUFixture *fixture,
+                                gconstpointer user_data)
+{
+       EBookCacheCursor *cursor;
+       GError *error = NULL;
+
+       cursor = e_book_cache_cursor_new (fixture->book_cache, NULL, NULL, NULL, 0, &error);
+
+       g_assert (cursor == NULL);
+       g_assert (error);
+       g_assert (g_error_matches (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY));
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       /* Ensure that the client and server get the same locale */
+       g_assert (g_setenv ("LC_ALL", "en_US.UTF-8", TRUE));
+       setlocale (LC_ALL, "");
+
+       g_test_add (
+               "/EBookCacheCursor/Create/EmptyQuery", TCUFixture, &closure,
+               tcu_fixture_setup, test_create_cursor_empty_query, tcu_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Create/ValidQuery", TCUFixture, &closure,
+               tcu_fixture_setup, test_create_cursor_valid_query, tcu_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Create/InvalidSort", TCUFixture, &closure,
+               tcu_fixture_setup, test_create_cursor_invalid_sort, tcu_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Create/MissingSort", TCUFixture, &closure,
+               tcu_fixture_setup, test_create_cursor_missing_sort, tcu_fixture_teardown);
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-calculate.c 
b/tests/libedata-book/test-cache-cursor-calculate.c
new file mode 100644
index 0000000..cf7172a
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-calculate.c
@@ -0,0 +1,695 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+static TCUCursorClosure ascending_closure = {
+       { NULL },
+       NULL, E_BOOK_CURSOR_SORT_ASCENDING
+};
+
+static TCUCursorClosure descending_closure = {
+       { NULL },
+       NULL, E_BOOK_CURSOR_SORT_DESCENDING
+};
+
+static void
+test_cursor_calculate_initial (TCUCursorFixture *fixture,
+                              gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+           g_error ("Error calculating cursor: %s", error->message);
+
+       g_assert_cmpint (position, ==, 0);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_move_forward (TCUCursorFixture *fixture,
+                                   gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                      5,
+                                      &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Assert the first 5 contacts in en_US order */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-11",
+               "sorted-1",
+               "sorted-2",
+               "sorted-5",
+               "sorted-6",
+               NULL);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 0 + 5 = position 5, result index 4 (results[0, 1, 2, 3, 4]) */
+       g_assert_cmpint (position, ==, 5);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_move_backwards (TCUCursorFixture *fixture,
+                                     gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_END,
+                                      -5,
+                                      &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Assert the last 5 contacts in en_US order */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-20",
+               "sorted-19",
+               "sorted-9",
+               "sorted-13",
+               "sorted-12",
+               NULL);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+           g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 20 - 5 = position 16 result index 15 (results[20, 19, 18, 17, 16]) */
+       g_assert_cmpint (position, ==, 16);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_back_and_forth (TCUCursorFixture *fixture,
+                                     gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                      7,
+                                      &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       g_assert_cmpint (g_slist_length (results), ==, 7);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 0 + 7 = position 7 result index 6 (results[0, 1, 2, 3, 4, 5, 6]) */
+       g_assert_cmpint (position, ==, 7);
+       g_assert_cmpint (total, ==, 20);
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                      -4,
+                                      &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       g_assert_cmpint (g_slist_length (results), ==, 4);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 7 - 4 = position 3 result index 2 (results[5, 4, 3, 2]) */
+       g_assert_cmpint (position, ==, 3);
+       g_assert_cmpint (total, ==, 20);
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                      5,
+                                      &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 3 + 5 = position 8 result index 7 (results[3, 4, 5, 6, 7]) */
+       g_assert_cmpint (position, ==, 8);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_partial_target (TCUCursorFixture *fixture,
+                                     gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+       ECollator *collator;
+       gint n_labels;
+       const gchar *const *labels;
+
+       /* First verify our test... in en_US locale the label 'C' should exist with the index 3 */
+       collator = e_book_cache_ref_collator (((TCUFixture *) fixture)->book_cache);
+       labels = e_collator_get_index_labels (collator, &n_labels, NULL, NULL, NULL);
+       g_assert_cmpstr (labels[3], ==, "C");
+       e_collator_unref (collator);
+
+       /* Set the cursor at the start of family names beginning with 'C' */
+       e_book_cache_cursor_set_target_alphabetic_index (
+               ((TCUFixture *) fixture)->book_cache,
+               fixture->cursor, 3);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* Position is 13, there are 13 contacts before the letter 'C' in en_US locale */
+       g_assert_cmpint (position, ==, 13);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_after_modification (TCUCursorFixture *fixture,
+                                         gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Set the cursor to point exactly 'blackbird' (which is the 12th contact) */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                      12, NULL, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* blackbird is at position 12 in en_US locale */
+       g_assert_cmpint (position, ==, 12);
+       g_assert_cmpint (total, ==, 20);
+
+       /* Rename Muffler -> Jacob Appelbaum */
+       e_contact_set (fixture->contacts[19 - 1], E_CONTACT_FAMILY_NAME, "Appelbaum");
+       e_contact_set (fixture->contacts[19 - 1], E_CONTACT_GIVEN_NAME, "Jacob");
+       if (!e_book_cache_put_contact (((TCUFixture *) fixture)->book_cache,
+                                       fixture->contacts[19 - 1],
+                                       e_contact_get_const (fixture->contacts[19 - 1], E_CONTACT_UID),
+                                       FALSE, NULL, &error))
+               g_error ("Failed to modify contact: %s", error->message);
+
+       /* Rename Müller -> Sade Adu */
+       e_contact_set (fixture->contacts[20 - 1], E_CONTACT_FAMILY_NAME, "Adu");
+       e_contact_set (fixture->contacts[20 - 1], E_CONTACT_GIVEN_NAME, "Sade");
+       if (!e_book_cache_put_contact (((TCUFixture *) fixture)->book_cache,
+                                       fixture->contacts[20 - 1],
+                                       e_contact_get_const (fixture->contacts[20 - 1], E_CONTACT_UID),
+                                       FALSE, NULL, &error))
+               g_error ("Failed to modify contact: %s", error->message);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* blackbird is now at position 14 after moving 2 later contacts to begin with 'A' */
+       g_assert_cmpint (position, ==, 14);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_filtered_initial (TCUCursorFixture *fixture,
+                                       gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+           g_error ("Error calculating cursor: %s", error->message);
+
+       g_assert_cmpint (position, ==, 0);
+       g_assert_cmpint (total, ==, 13);
+}
+
+static void
+test_cursor_calculate_filtered_move_forward (TCUCursorFixture *fixture,
+                                            gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                      5, &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+           g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 0 + 5 = position 5, result index 4 (results[0, 1, 2, 3, 4]) */
+       g_assert_cmpint (position, ==, 5);
+       g_assert_cmpint (total, ==, 13);
+}
+
+static void
+test_cursor_calculate_filtered_move_backwards (TCUCursorFixture *fixture,
+                                              gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_END,
+                                      -5,
+                                      &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 13 - 5 = position 9 (results[13, 12, 11, 10, 9]) */
+       g_assert_cmpint (position, ==, 9);
+       g_assert_cmpint (total, ==, 13);
+}
+
+static void
+test_cursor_calculate_filtered_partial_target (TCUCursorFixture *fixture,
+                                              gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+       ECollator *collator;
+       gint n_labels;
+       const gchar *const *labels;
+
+       /* First verify our test... in en_US locale the label 'C' should exist with the index 3 */
+       collator = e_book_cache_ref_collator (((TCUFixture *) fixture)->book_cache);
+       labels = e_collator_get_index_labels (collator, &n_labels, NULL, NULL, NULL);
+       g_assert_cmpstr (labels[3], ==, "C");
+       e_collator_unref (collator);
+
+       /* Set the cursor at the start of family names beginning with 'C' */
+       e_book_cache_cursor_set_target_alphabetic_index (
+               ((TCUFixture *) fixture)->book_cache,
+               fixture->cursor, 3);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* There are 9 contacts before the letter 'C' in the en_US locale */
+       g_assert_cmpint (position, ==, 9);
+       g_assert_cmpint (total, ==, 13);
+}
+
+static void
+test_cursor_calculate_filtered_after_modification (TCUCursorFixture *fixture,
+                                                  gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Set the cursor to point exactly 'blackbird' (which is the 8th contact when filtered) */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                      8, NULL, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* 'blackbirds' -> Jacob Appelbaum */
+       e_contact_set (fixture->contacts[18 - 1], E_CONTACT_FAMILY_NAME, "Appelbaum");
+       e_contact_set (fixture->contacts[18 - 1], E_CONTACT_GIVEN_NAME, "Jacob");
+       if (!e_book_cache_put_contact (((TCUFixture *) fixture)->book_cache,
+                                       fixture->contacts[18 - 1],
+                                       e_contact_get_const (fixture->contacts[18 - 1], E_CONTACT_UID),
+                                       FALSE, NULL, &error))
+               g_error ("Failed to modify contact: %s", error->message);
+
+       /* 'black-birds' -> Sade Adu */
+       e_contact_set (fixture->contacts[17 - 1], E_CONTACT_FAMILY_NAME, "Adu");
+       e_contact_set (fixture->contacts[17 - 1], E_CONTACT_GIVEN_NAME, "Sade");
+       if (!e_book_cache_put_contact (((TCUFixture *) fixture)->book_cache,
+                                       fixture->contacts[17 - 1],
+                                       e_contact_get_const (fixture->contacts[17 - 1], E_CONTACT_UID),
+                                       FALSE, NULL, &error))
+               g_error ("Failed to modify contact: %s", error->message);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* blackbird is now at position 11 after moving 2 later contacts to begin with 'A' */
+       g_assert_cmpint (position, ==, 9);
+       g_assert_cmpint (total, ==, 13);
+}
+
+static void
+test_cursor_calculate_descending_move_forward (TCUCursorFixture *fixture,
+                                               gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                      5,
+                                      &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Assert the first 5 contacts in en_US order */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-20",
+               "sorted-19",
+               "sorted-9",
+               "sorted-13",
+               "sorted-12",
+               NULL);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+           g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 0 + 5 = position 5, result index 4 (results[0, 1, 2, 3, 4]) */
+       g_assert_cmpint (position, ==, 5);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_descending_move_backwards (TCUCursorFixture *fixture,
+                                                gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Move cursor */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_END,
+                                      -5, &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Assert the last 5 contacts in en_US order */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-11",
+               "sorted-1",
+               "sorted-2",
+               "sorted-5",
+               "sorted-6",
+               NULL);
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+           g_error ("Error calculating cursor: %s", error->message);
+
+       /* results 20 - 5 = position 16 result index 15 (results[20, 19, 18, 17, 16]) */
+       g_assert_cmpint (position, ==, 16);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_descending_partial_target (TCUCursorFixture *fixture,
+                                                gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+       ECollator *collator;
+       gint n_labels;
+       const gchar *const *labels;
+
+       /* First verify our test... in en_US locale the label 'C' should exist with the index 3 */
+       collator = e_book_cache_ref_collator (((TCUFixture *) fixture)->book_cache);
+       labels = e_collator_get_index_labels (collator, &n_labels, NULL, NULL, NULL);
+       g_assert_cmpstr (labels[3], ==, "C");
+       e_collator_unref (collator);
+
+       /* Set the cursor at the start of family names beginning with 'C' */
+       e_book_cache_cursor_set_target_alphabetic_index (
+               ((TCUFixture *) fixture)->book_cache,
+               fixture->cursor, 3);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* Position is 7, there are 7 contacts leading up to the last 'C' in en_US locale
+        * (when sorting in descending order) */
+       g_assert_cmpint (position, ==, 7);
+       g_assert_cmpint (total, ==, 20);
+}
+
+static void
+test_cursor_calculate_descending_after_modification (TCUCursorFixture *fixture,
+                                                    gconstpointer user_data)
+{
+       GError *error = NULL;
+       gint position = 0, total = 0;
+
+       /* Set the cursor to point exactly 'Bät' (which is the 12th contact in descending order) */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                      12, NULL, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* 'Bät' is at position 12 in en_US locale (descending order) */
+       g_assert_cmpint (position, ==, 12);
+       g_assert_cmpint (total, ==, 20);
+
+       /* Rename Muffler -> Jacob Appelbaum */
+       e_contact_set (fixture->contacts[19 - 1], E_CONTACT_FAMILY_NAME, "Appelbaum");
+       e_contact_set (fixture->contacts[19 - 1], E_CONTACT_GIVEN_NAME, "Jacob");
+       if (!e_book_cache_put_contact (((TCUFixture *) fixture)->book_cache,
+                                       fixture->contacts[19 - 1],
+                                       e_contact_get_const (fixture->contacts[19 - 1], E_CONTACT_UID),
+                                       FALSE, NULL, &error))
+               g_error ("Failed to modify contact: %s", error->message);
+
+       /* Rename Müller -> Sade Adu */
+       e_contact_set (fixture->contacts[20 - 1], E_CONTACT_FAMILY_NAME, "Adu");
+       e_contact_set (fixture->contacts[20 - 1], E_CONTACT_GIVEN_NAME, "Sade");
+       if (!e_book_cache_put_contact (((TCUFixture *) fixture)->book_cache,
+                                       fixture->contacts[20 - 1],
+                                       e_contact_get_const (fixture->contacts[20 - 1], E_CONTACT_UID),
+                                       FALSE, NULL, &error))
+               g_error ("Failed to modify contact: %s", error->message);
+
+       /* Check new position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* 'Bät' is now at position 10 in descending order after moving 2 contacts to begin with 'A' */
+       g_assert_cmpint (position, ==, 10);
+       g_assert_cmpint (total, ==, 20);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Initial", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_initial,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/MoveForward", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_move_forward,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/MoveBackwards", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_move_backwards,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/BackAndForth", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_back_and_forth,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/AlphabeticTarget", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_partial_target,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/AfterModification", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_after_modification,
+               tcu_cursor_fixture_teardown);
+
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Filtered/Initial", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_filtered_setup,
+               test_cursor_calculate_filtered_initial,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Filtered/MoveForward", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_filtered_setup,
+               test_cursor_calculate_filtered_move_forward,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Filtered/MoveBackwards", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_filtered_setup,
+               test_cursor_calculate_filtered_move_backwards,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Filtered/AlphabeticTarget", TCUCursorFixture, &ascending_closure,
+               tcu_cursor_fixture_filtered_setup,
+               test_cursor_calculate_filtered_partial_target,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Filtered/AfterModification", TCUCursorFixture, 
&ascending_closure,
+               tcu_cursor_fixture_filtered_setup,
+               test_cursor_calculate_filtered_after_modification,
+               tcu_cursor_fixture_teardown);
+
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Descending/Initial", TCUCursorFixture, &descending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_initial,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Descending/MoveForward", TCUCursorFixture, &descending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_descending_move_forward,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Descending/MoveBackwards", TCUCursorFixture, &descending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_descending_move_backwards,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Descending/BackAndForth", TCUCursorFixture, &descending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_back_and_forth,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Descending/AlphabeticTarget", TCUCursorFixture, 
&descending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_descending_partial_target,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/Calculate/Descending/AfterModification", TCUCursorFixture, 
&descending_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_calculate_descending_after_modification,
+               tcu_cursor_fixture_teardown);
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-change-locale.c 
b/tests/libedata-book/test-cache-cursor-change-locale.c
new file mode 100644
index 0000000..2445652
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-change-locale.c
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+struct {
+       gboolean empty_book;
+       const gchar *path;
+} params[] = {
+       { FALSE, "/EBookCacheCursor/DefaultSummary" },
+       { TRUE,  "/EBookCacheCursor/EmptySummary" }
+};
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       TCUStepData *data;
+       gint ii;
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       for (ii = 0; ii < G_N_ELEMENTS (params); ii++) {
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/ChangeLocale/POSIX/en_US", "POSIX",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 2,  6,  3,  8);
+               tcu_step_test_add_assertion (data, 5, 1,  5,  4,  7,  15);
+               tcu_step_test_add_assertion (data, 5, 17, 16, 18, 10, 14);
+               tcu_step_test_add_assertion (data, 5, 12, 13, 9,  19, 20);
+
+               tcu_step_test_change_locale (data, "en_US.UTF-8", 0);
+               tcu_step_test_add_assertion (data, 5, 11, 1,  2,  5,  6);
+               tcu_step_test_add_assertion (data, 5, 4,  3,  7,  8,  15);
+               tcu_step_test_add_assertion (data, 5, 17, 16, 18, 10, 14);
+               tcu_step_test_add_assertion (data, 5, 12, 13, 9,  19, 20);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/ChangeLocale/en_US/fr_CA", "en_US.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1,  2,  5,  6);
+               tcu_step_test_add_assertion (data, 5, 4,  3,  7,  8,  15);
+               tcu_step_test_add_assertion (data, 5, 17, 16, 18, 10, 14);
+               tcu_step_test_add_assertion (data, 5, 12, 13, 9,  19, 20);
+
+               tcu_step_test_change_locale (data, "fr_CA.UTF-8", 0);
+               tcu_step_test_add_assertion (data, 5, 11, 1,  2,  5,  6);
+               tcu_step_test_add_assertion (data, 5, 4,  3,  7,  8,  15);
+               tcu_step_test_add_assertion (data, 5, 17, 16, 18, 10, 14);
+               tcu_step_test_add_assertion (data, 5, 13, 12, 9,  19, 20);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/ChangeLocale/fr_CA/de_DE", "fr_CA.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1,  2,  5,  6);
+               tcu_step_test_add_assertion (data, 5, 4,  3,  7,  8,  15);
+               tcu_step_test_add_assertion (data, 5, 17, 16, 18, 10, 14);
+               tcu_step_test_add_assertion (data, 5, 13, 12, 9,  19, 20);
+
+               /* When changing from fr_CA to de_DE, two numbers change:
+                *
+                * sorted-5:
+                *    049-2459-4393 is now parsed with the national number as 4924594393
+                *
+                * sorted-4:
+                *    12 245999 is now parsed with national number 12245999 instead of 2245999
+                *
+                */
+               tcu_step_test_change_locale (data, "de_DE.UTF-8", 2);
+               tcu_step_test_add_assertion (data, 5, 11, 1,  2,  5,  6);
+               tcu_step_test_add_assertion (data, 5, 7,  8,  4,  3,  15);
+               tcu_step_test_add_assertion (data, 5, 17, 16, 18, 10, 14);
+               tcu_step_test_add_assertion (data, 5, 12, 13, 9,  20, 19);
+               tcu_step_test_add (data, FALSE);
+       }
+
+       /* On this case, we want to delete the work directory and start fresh */
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-move-by-de-DE.c 
b/tests/libedata-book/test-cache-cursor-move-by-de-DE.c
new file mode 100644
index 0000000..b4bfb2c
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-move-by-de-DE.c
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+struct {
+       gboolean empty_book;
+       const gchar *path;
+} params[] = {
+       { FALSE, "/EBookCacheCursor/DefaultSummary" },
+       { TRUE,  "/EBookCacheCursor/EmptySummary" }
+};
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       TCUStepData *data;
+       gint ii;
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       for (ii = 0; ii < G_N_ELEMENTS (params); ii++) {
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/de_DE/Move/Forward", "de_DE.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1, 2, 5, 6);
+               tcu_step_test_add_assertion (data, 6, 7, 8, 4, 3, 15, 17);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/de_DE/Move/ForwardOnNameless", "de_DE.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 1, 11);
+               tcu_step_test_add_assertion (data, 3, 1, 2, 5);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/de_DE/Move/Backwards", "de_DE.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 19, 20, 9, 13, 12);
+               tcu_step_test_add_assertion (data, -8, 14, 10, 18, 16, 17, 15, 3, 4);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/de_DE/Filtered/Move/Forward", "de_DE.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1, 2, 5, 8);
+               tcu_step_test_add_assertion (data, 8, 3, 17, 16, 18, 10, 14, 12, 9);
+               tcu_step_test_add (data, TRUE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/de_DE/Filtered/Move/Backwards", "de_DE.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 9, 12, 14, 10, 18);
+               tcu_step_test_add_assertion (data, -8, 16, 17, 3, 8, 5, 2, 1, 11);
+               tcu_step_test_add (data, TRUE);
+       }
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-move-by-en-US.c 
b/tests/libedata-book/test-cache-cursor-move-by-en-US.c
new file mode 100644
index 0000000..6f969e7
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-move-by-en-US.c
@@ -0,0 +1,100 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+struct {
+       gboolean empty_book;
+       const gchar *path;
+} params[] = {
+       { FALSE, "/EBookCacheCursor/DefaultSummary" },
+       { TRUE,  "/EBookCacheCursor/EmptySummary" }
+};
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       TCUStepData *data;
+       gint ii;
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       for (ii = 0; ii < G_N_ELEMENTS (params); ii++) {
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/en_US/Move/Forward", "en_US.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1, 2, 5, 6);
+               tcu_step_test_add_assertion (data, 6, 4, 3, 7, 8, 15, 17);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/en_US/Move/ForwardOnNameless", "en_US.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 1, 11);
+               tcu_step_test_add_assertion (data, 3, 1, 2, 5);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/en_US/Move/Backwards", "en_US.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 20, 19, 9, 13, 12);
+               tcu_step_test_add_assertion (data, -8, 14, 10, 18, 16, 17, 15, 8, 7);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/en_US/Filtered/Move/Forward", "en_US.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1, 2, 5, 3);
+               tcu_step_test_add_assertion (data, 8, 8, 17, 16, 18, 10, 14, 12, 9);
+               tcu_step_test_add (data, TRUE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/en_US/Filtered/Move/Backwards", "en_US.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 9, 12, 14, 10, 18);
+               tcu_step_test_add_assertion (data, -8, 16, 17, 8, 3, 5, 2, 1, 11);
+               tcu_step_test_add (data, TRUE);
+
+               data = tcu_step_test_new_full (
+                       params[ii].path, "/en_US/Move/Descending/Forward", "en_US.UTF-8",
+                       params[ii].empty_book,
+                       E_BOOK_CURSOR_SORT_DESCENDING);
+               tcu_step_test_add_assertion (data, 5, 20, 19, 9,  13, 12);
+               tcu_step_test_add_assertion (data, 5, 14, 10, 18, 16, 17);
+               tcu_step_test_add_assertion (data, 5, 15, 8,  7,  3,  4);
+               tcu_step_test_add_assertion (data, 5, 6,  5,  2,  1,  11);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new_full (
+                       params[ii].path, "/en_US/Move/Descending/Backwards", "en_US.UTF-8",
+                       params[ii].empty_book,
+                       E_BOOK_CURSOR_SORT_DESCENDING);
+               tcu_step_test_add_assertion (data, -10, 11, 1,  2,  5,  6,  4,  3,  7,  8, 15);
+               tcu_step_test_add_assertion (data, -10, 17, 16, 18, 10, 14, 12, 13, 9, 19, 20);
+               tcu_step_test_add (data, FALSE);
+       }
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-move-by-fr-CA.c 
b/tests/libedata-book/test-cache-cursor-move-by-fr-CA.c
new file mode 100644
index 0000000..7bfb435
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-move-by-fr-CA.c
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+struct {
+       gboolean empty_book;
+       const gchar *path;
+} params[] = {
+       { FALSE, "/EBookCacheCursor/DefaultSummary" },
+       { TRUE,  "/EBookCacheCursor/EmptySummary" }
+};
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       TCUStepData *data;
+       gint ii;
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       for (ii = 0; ii < G_N_ELEMENTS (params); ii++) {
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/fr_CA/Move/Forward", "fr_CA.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1, 2, 5, 6);
+               tcu_step_test_add_assertion (data, 6, 4, 3, 7, 8, 15, 17);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/fr_CA/Move/ForwardOnNameless", "fr_CA.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 1, 11);
+               tcu_step_test_add_assertion (data, 3, 1, 2, 5);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/fr_CA/Move/Backwards", "fr_CA.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 20, 19, 9, 12, 13);
+               tcu_step_test_add_assertion (data, -8, 14, 10, 18, 16, 17, 15, 8, 7);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/fr_CA/Filtered/Move/Forward", "fr_CA.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 1, 2, 5, 3);
+               tcu_step_test_add_assertion (data, 8, 8, 17, 16, 18, 10, 14, 12, 9);
+               tcu_step_test_add (data, TRUE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/fr_CA/Filtered/Move/Backwards", "fr_CA.UTF-8",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 9, 12, 14, 10, 18);
+               tcu_step_test_add_assertion (data, -8, 16, 17, 8, 3, 5, 2, 1, 11);
+               tcu_step_test_add (data, TRUE);
+       }
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-move-by-posix.c 
b/tests/libedata-book/test-cache-cursor-move-by-posix.c
new file mode 100644
index 0000000..93d4e05
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-move-by-posix.c
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+struct {
+       gboolean empty_book;
+       const gchar *path;
+} params[] = {
+       { FALSE, "/EBookCacheCursor/DefaultSummary" },
+       { TRUE,  "/EBookCacheCursor/EmptySummary" }
+};
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       TCUStepData *data;
+       gint ii;
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       for (ii = 0; ii < G_N_ELEMENTS (params); ii++) {
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/POSIX/Move/Forward", "POSIX",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 2, 6, 3, 8);
+               tcu_step_test_add_assertion (data, 6, 1,  5,  4,  7,  15, 17);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/POSIX/Move/ForwardOnNameless", "POSIX",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 1, 11);
+               tcu_step_test_add_assertion (data, 3, 2, 6, 3);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/POSIX/Move/Backwards", "POSIX",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 20, 19, 9, 13, 12);
+               tcu_step_test_add_assertion (data, -12, 14, 10, 18, 16, 17, 15, 7, 4, 5, 1, 8, 3);
+               tcu_step_test_add (data, FALSE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/POSIX/Filtered/Move/Forward", "POSIX",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, 5, 11, 2, 3, 8, 1);
+               tcu_step_test_add_assertion (data, 8, 5, 17, 16, 18, 10, 14, 12, 9);
+               tcu_step_test_add (data, TRUE);
+
+               data = tcu_step_test_new (
+                       params[ii].path, "/POSIX/Filtered/Move/Backwards", "POSIX",
+                       params[ii].empty_book);
+               tcu_step_test_add_assertion (data, -5, 9, 12, 14, 10, 18);
+               tcu_step_test_add_assertion (data, -8, 16, 17, 5, 1, 8, 3, 2, 11);
+               tcu_step_test_add (data, TRUE);
+       }
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-set-sexp.c 
b/tests/libedata-book/test-cache-cursor-set-sexp.c
new file mode 100644
index 0000000..94bc247
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-set-sexp.c
@@ -0,0 +1,151 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+static TCUCursorClosure book_closure = { { NULL }, NULL, E_BOOK_CURSOR_SORT_ASCENDING };
+
+static void
+test_cursor_sexp_calculate_position (TCUCursorFixture *fixture,
+                                    gconstpointer user_data)
+{
+       GError *error = NULL;
+       EBookQuery *query;
+       gint    position = 0, total = 0;
+       gchar *sexp = NULL;
+       GSList *results = NULL, *node;
+       EBookCacheSearchData *data;
+
+       /* Set the cursor to point exactly to 'blackbirds', which is the 12th contact in en_US */
+       if (!e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                       fixture->cursor,
+                                       E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                       E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                       12, &results, NULL, &error))
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Ensure we moved to the right contact */
+       node = g_slist_last (results);
+       g_assert (node);
+       data = node->data;
+       g_assert_cmpstr (data->uid, ==, "sorted-16");
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+
+       /* Check position */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* blackbird is at position 12 in an unfiltered en_US locale */
+       g_assert_cmpint (position, ==, 12);
+       g_assert_cmpint (total, ==, 20);
+
+       /* Set new sexp, only contacts with .com email addresses */
+       query = e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_ENDS_WITH, ".com");
+       sexp = e_book_query_to_string (query);
+       e_book_query_unref (query);
+
+       if (!e_book_cache_cursor_set_sexp (((TCUFixture *) fixture)->book_cache,
+                                           fixture->cursor, sexp, &error))
+               g_error ("Failed to set sexp: %s", error->message);
+
+       /* Check new position after modified sexp */
+       if (!e_book_cache_cursor_calculate (((TCUFixture *) fixture)->book_cache,
+                                            fixture->cursor, &total, &position, NULL, &error))
+               g_error ("Error calculating cursor: %s", error->message);
+
+       /* 'blackbird' is now at position 8 out of 13, with a filtered set of contacts in en_US locale */
+       g_assert_cmpint (position, ==, 8);
+       g_assert_cmpint (total, ==, 13);
+}
+
+static void
+test_cursor_sexp_and_step (TCUCursorFixture *fixture,
+                          gconstpointer user_data)
+{
+       GError *error = NULL;
+       EBookQuery *query;
+       gchar *sexp = NULL;
+       GSList *results = NULL, *node;
+       EBookCacheSearchData *data;
+
+       /* Set new sexp, only contacts with .com email addresses */
+       query = e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_ENDS_WITH, ".com");
+       sexp = e_book_query_to_string (query);
+       e_book_query_unref (query);
+
+       if (!e_book_cache_cursor_set_sexp (((TCUFixture *) fixture)->book_cache,
+                                           fixture->cursor, sexp, &error))
+               g_error ("Failed to set sexp: %s", error->message);
+
+       /* Step 6 results from the beginning of the filtered list, gets up to contact 'sorted-8' */
+       if (!e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                       fixture->cursor,
+                                       E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                       E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                       6, &results, NULL, &error))
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Ensure we moved to the right contact */
+       node = g_slist_last (results);
+       g_assert (node);
+       data = node->data;
+       g_assert_cmpstr (data->uid, ==, "sorted-8");
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Step 6 results more, gets up to contact 'sorted-12' */
+       if (!e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                       fixture->cursor,
+                                       E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                       E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                       6, &results, NULL, &error))
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       /* Ensure we moved to the right contact */
+       node = g_slist_last (results);
+       g_assert (node);
+       data = node->data;
+       g_assert_cmpstr (data->uid, ==, "sorted-12");
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add (
+               "/EBookCacheCursor/SetSexp/CalculatePosition", TCUCursorFixture, &book_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_sexp_calculate_position,
+               tcu_cursor_fixture_teardown);
+       g_test_add (
+               "/EBookCacheCursor/SetSexp/Step", TCUCursorFixture, &book_closure,
+               tcu_cursor_fixture_setup,
+               test_cursor_sexp_and_step,
+               tcu_cursor_fixture_teardown);
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-cursor-set-target.c 
b/tests/libedata-book/test-cache-cursor-set-target.c
new file mode 100644
index 0000000..ade53c5
--- /dev/null
+++ b/tests/libedata-book/test-cache-cursor-set-target.c
@@ -0,0 +1,225 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+/*****************************************************
+ *          Expect the same results twice            *
+ *****************************************************/
+static void
+test_cursor_set_target_reset_cursor (TCUCursorFixture *fixture,
+                                    gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+
+       /* First batch */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                      5, &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       tcu_print_results (results);
+
+       /* Assert the first 5 contacts in en_US order */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-11",
+               "sorted-1",
+               "sorted-2",
+               "sorted-5",
+               "sorted-6",
+               NULL);
+
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+       results = NULL;
+
+       /* Second batch reset (same results) */
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN,
+                                      5, &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       tcu_print_results (results);
+
+       /* Assert the first 5 contacts in en_US order again */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-11",
+               "sorted-1",
+               "sorted-2",
+               "sorted-5",
+               "sorted-6",
+               NULL);
+
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+}
+
+/*****************************************************
+ * Expect results with family name starting with 'C' *
+ *****************************************************/
+static void
+test_cursor_set_target_c_next_results (TCUCursorFixture *fixture,
+                                      gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       ECollator *collator;
+       gint n_labels;
+       const gchar *const *labels;
+
+       /* First verify our test... in en_US locale the label 'C' should exist with the index 3 */
+       collator = e_book_cache_ref_collator (((TCUFixture *) fixture)->book_cache);
+       labels = e_collator_get_index_labels (collator, &n_labels, NULL, NULL, NULL);
+       g_assert_cmpstr (labels[3], ==, "C");
+       e_collator_unref (collator);
+
+       /* Set the cursor at the start of family names beginning with 'C' */
+       e_book_cache_cursor_set_target_alphabetic_index (
+               ((TCUFixture *) fixture)->book_cache,
+               fixture->cursor, 3);
+
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                      5, &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       tcu_print_results (results);
+
+       /* Assert that we got the results starting at C */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-10",
+               "sorted-14",
+               "sorted-12",
+               "sorted-13",
+               "sorted-9",
+               NULL);
+
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+}
+
+/*****************************************************
+ *       Expect results before the letter 'C'        *
+ *****************************************************/
+static void
+test_cursor_set_target_c_prev_results (TCUCursorFixture *fixture,
+                                       gconstpointer user_data)
+{
+       GSList *results = NULL;
+       GError *error = NULL;
+       ECollator *collator;
+       gint n_labels;
+       const gchar *const *labels;
+
+       /* First verify our test... in en_US locale the label 'C' should exist with the index 3 */
+       collator = e_book_cache_ref_collator (((TCUFixture *) fixture)->book_cache);
+       labels = e_collator_get_index_labels (collator, &n_labels, NULL, NULL, NULL);
+       g_assert_cmpstr (labels[3], ==, "C");
+       e_collator_unref (collator);
+
+       /* Set the cursor at the start of family names beginning with 'C' */
+       e_book_cache_cursor_set_target_alphabetic_index (
+               ((TCUFixture *) fixture)->book_cache,
+               fixture->cursor, 3);
+
+       if (e_book_cache_cursor_step (((TCUFixture *) fixture)->book_cache,
+                                      fixture->cursor,
+                                      E_BOOK_CACHE_CURSOR_STEP_MOVE | E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                                      E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT,
+                                      -5, &results, NULL, &error) < 0)
+               g_error ("Error fetching cursor results: %s", error->message);
+
+       tcu_print_results (results);
+
+       /* Assert that we got the results before C */
+       g_assert_cmpint (g_slist_length (results), ==, 5);
+       tcu_assert_contacts_order (
+               results,
+               "sorted-18",
+               "sorted-16",
+               "sorted-17",
+               "sorted-15",
+               "sorted-8",
+               NULL);
+
+       g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+}
+
+static TCUCursorClosure closures[] = {
+       { { NULL }, NULL, E_BOOK_CURSOR_SORT_ASCENDING },
+       { { tcu_setup_empty_book }, NULL, E_BOOK_CURSOR_SORT_ASCENDING }
+};
+
+static const gchar *prefixes[] = {
+       "/EBookCache/DefaultSummary",
+       "/EBookCache/EmptySummary"
+};
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       gint ii;
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       for (ii = 0; ii < G_N_ELEMENTS (closures); ii++) {
+               gchar *path;
+
+               path = g_strconcat (prefixes[ii], "/SetTarget/ResetCursor", NULL);
+               g_test_add (
+                       path, TCUCursorFixture, &closures[ii],
+                       tcu_cursor_fixture_setup,
+                       test_cursor_set_target_reset_cursor,
+                       tcu_cursor_fixture_teardown);
+               g_free (path);
+
+               path = g_strconcat (prefixes[ii], "/SetTarget/Alphabetic/C/NextResults", NULL);
+               g_test_add (
+                       path, TCUCursorFixture, &closures[ii],
+                       tcu_cursor_fixture_setup,
+                       test_cursor_set_target_c_next_results,
+                       tcu_cursor_fixture_teardown);
+               g_free (path);
+
+               path = g_strconcat (prefixes[ii], "/SetTarget/Alphabetic/C/PreviousResults", NULL);
+               g_test_add (
+                       path, TCUCursorFixture, &closures[ii],
+                       tcu_cursor_fixture_setup,
+                       test_cursor_set_target_c_prev_results,
+                       tcu_cursor_fixture_teardown);
+               g_free (path);
+       }
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-get-contact.c b/tests/libedata-book/test-cache-get-contact.c
new file mode 100644
index 0000000..d48faed
--- /dev/null
+++ b/tests/libedata-book/test-cache-get-contact.c
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libebook/libebook.h>
+
+#include "test-cache-utils.h"
+
+static void
+test_get_contact (TCUFixture *fixture,
+                  gconstpointer user_data)
+{
+       EContact *contact = NULL;
+       EContact *other = NULL;
+       GError *error = NULL;
+
+       tcu_add_contact_from_test_case (fixture, "simple-1", &contact);
+
+       if (!e_book_cache_get_contact (fixture->book_cache,
+               (const gchar *) e_contact_get_const (contact, E_CONTACT_UID),
+               FALSE, &other, NULL, &error)) {
+               g_error (
+                       "Failed to get contact with uid '%s': %s",
+                       (const gchar *) e_contact_get_const (contact, E_CONTACT_UID),
+                       error->message);
+       }
+
+       g_object_unref (contact);
+       g_object_unref (other);
+}
+
+static TCUClosure closures[] = {
+       { NULL },
+       { tcu_setup_empty_book }
+};
+
+static const gchar *paths[] = {
+       "/EBookCache/DefaultSummary/GetContact",
+       "/EBookCache/EmptySummary/GetContact",
+};
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       gint ii;
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       /* Ensure that the client and server get the same locale */
+       g_assert (g_setenv ("LC_ALL", "en_US.UTF-8", TRUE));
+       setlocale (LC_ALL, "");
+
+       for (ii = 0; ii < G_N_ELEMENTS (closures); ii++) {
+               g_test_add (
+                       paths[ii], TCUFixture, &closures[ii],
+                       tcu_fixture_setup, test_get_contact, tcu_fixture_teardown);
+       }
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-book/test-cache-utils.c b/tests/libedata-book/test-cache-utils.c
new file mode 100644
index 0000000..70a74d4
--- /dev/null
+++ b/tests/libedata-book/test-cache-utils.c
@@ -0,0 +1,691 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013, Openismus GmbH
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Tristan Van Berkom <tristanvb openismus com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "test-cache-utils.h"
+
+gchar *
+tcu_new_vcard_from_test_case (const gchar *case_name)
+{
+       gchar *filename;
+       gchar *case_filename;
+       GFile * file;
+       GError *error = NULL;
+       gchar *vcard;
+
+       case_filename = g_strdup_printf ("%s.vcf", case_name);
+
+       /* In the case of installed tests, they run in ${pkglibexecdir}/installed-tests
+        * and the vcards are installed in ${pkglibexecdir}/installed-tests/vcards
+        */
+       if (g_getenv ("TEST_INSTALLED_SERVICES") != NULL)
+               filename = g_build_filename (INSTALLED_TEST_DIR, "vcards", case_filename, NULL);
+       else
+               filename = g_build_filename (SRCDIR, "..", "libebook", "data", "vcards", case_filename, NULL);
+
+       file = g_file_new_for_path (filename);
+       if (!g_file_load_contents (file, NULL, &vcard, NULL, NULL, &error))
+               g_error (
+                       "failed to read test contact file '%s': %s",
+                       filename, error->message);
+
+       g_free (case_filename);
+       g_free (filename);
+       g_object_unref (file);
+
+       return vcard;
+}
+
+EContact *
+tcu_new_contact_from_test_case (const gchar *case_name)
+{
+       gchar *vcard;
+       EContact *contact = NULL;
+
+       vcard = tcu_new_vcard_from_test_case (case_name);
+       if (vcard)
+               contact = e_contact_new_from_vcard (vcard);
+       g_free (vcard);
+
+       if (!contact)
+               g_error (
+                       "failed to construct contact from test case '%s'",
+                       case_name);
+
+       return contact;
+}
+
+void
+tcu_add_contact_from_test_case (TCUFixture *fixture,
+                               const gchar *case_name,
+                               EContact **ret_contact)
+{
+       EContact *contact;
+       GError *error = NULL;
+
+       contact = tcu_new_contact_from_test_case (case_name);
+
+       if (!e_book_cache_put_contact (fixture->book_cache, contact, case_name, FALSE, NULL, &error))
+               g_error ("Failed to add contact: %s", error->message);
+
+       if (ret_contact)
+               *ret_contact = g_object_ref (contact);
+}
+
+static void
+delete_work_directory (const gchar *filename)
+{
+       /* XXX Instead of complex error checking here, we should ideally use
+        * a recursive GDir / g_unlink() function.
+        *
+        * We cannot use GFile and the recursive delete function without
+        * corrupting our contained D-Bus environment with service files
+        * from the OS.
+        */
+       const gchar *argv[] = { "/bin/rm", "-rf", filename, NULL };
+       gboolean spawn_succeeded;
+       gint exit_status;
+
+       spawn_succeeded = g_spawn_sync (
+               NULL, (gchar **) argv, NULL, 0, NULL, NULL,
+                                       NULL, NULL, &exit_status, NULL);
+
+       g_assert (spawn_succeeded);
+       #ifndef G_OS_WIN32
+       g_assert (WIFEXITED (exit_status));
+       g_assert_cmpint (WEXITSTATUS (exit_status), ==, 0);
+       #else
+       g_assert_cmpint (exit_status, ==, 0);
+       #endif
+}
+
+ESourceBackendSummarySetup *
+tcu_setup_empty_book (void)
+{
+       ESourceBackendSummarySetup *setup;
+       ESource *scratch;
+       GError *error = NULL;
+
+       scratch = e_source_new_with_uid ("test-source", NULL, &error);
+       if (!scratch)
+               g_error ("Error creating scratch source: %s", error ? error->message : "Unknown error");
+
+       /* This is a bit of a cheat */
+       setup = g_object_new (E_TYPE_SOURCE_BACKEND_SUMMARY_SETUP, "source", scratch, NULL);
+       e_source_backend_summary_setup_set_summary_fields (
+               setup,
+               /* We don't use this field in our tests anyway */
+               E_CONTACT_FILE_AS,
+               0);
+
+       g_object_unref (scratch);
+
+       return setup;
+}
+
+static void
+e164_changed_cb (EBookCache *book_cache,
+                EContact *contact,
+                gboolean is_replace,
+                gpointer user_data)
+{
+       TCUFixture *fixture = user_data;
+
+       if (is_replace)
+               fixture->n_locale_changes++;
+       else
+               fixture->n_add_changes++;
+}
+
+void
+tcu_fixture_setup (TCUFixture *fixture,
+                  gconstpointer user_data)
+{
+       TCUClosure *closure = (TCUClosure *) user_data;
+       ESourceBackendSummarySetup *setup = NULL;
+       gchar *filename, *directory;
+       GError *error = NULL;
+
+       if (!g_file_test (CAMEL_PROVIDERDIR, G_FILE_TEST_IS_DIR | G_FILE_TEST_EXISTS)) {
+               if (g_mkdir_with_parents (CAMEL_PROVIDERDIR, 0700) == -1)
+                       g_warning ("%s: Failed to create folder '%s': %s\n", G_STRFUNC, CAMEL_PROVIDERDIR, 
g_strerror (errno));
+       }
+
+       /* Cleanup from last test */
+       directory = g_build_filename (g_get_tmp_dir (), "test-book-cache", NULL);
+       delete_work_directory (directory);
+       g_free (directory);
+       filename = g_build_filename (g_get_tmp_dir (), "test-book-cache", "cache.db", NULL);
+
+       if (closure->setup_summary)
+               setup = closure->setup_summary ();
+
+       fixture->book_cache = e_book_cache_new_full (filename, NULL, setup, NULL, &error);
+
+       g_clear_object (&setup);
+
+       if (!fixture->book_cache)
+               g_error ("Failed to create the EBookCache: %s", error->message);
+
+       g_free (filename);
+
+       g_signal_connect (fixture->book_cache, "e164-changed",
+               G_CALLBACK (e164_changed_cb), fixture);
+}
+
+void
+tcu_fixture_teardown (TCUFixture *fixture,
+                     gconstpointer user_data)
+{
+       g_object_unref (fixture->book_cache);
+}
+
+void
+tcu_cursor_fixture_setup (TCUCursorFixture *fixture,
+                         gconstpointer user_data)
+{
+       TCUFixture *base_fixture = (TCUFixture   *) fixture;
+       TCUCursorClosure *data = (TCUCursorClosure *) user_data;
+       EContactField sort_fields[] = { E_CONTACT_FAMILY_NAME, E_CONTACT_GIVEN_NAME };
+       EBookCursorSortType sort_types[] = { data->sort_type, data->sort_type };
+       GSList *contacts = NULL;
+       GSList *extra_list = NULL;
+       GError *error = NULL;
+       gint ii;
+       gchar *sexp = NULL;
+
+       tcu_fixture_setup (base_fixture, user_data);
+
+       if (data->locale)
+               tcu_cursor_fixture_set_locale (fixture, data->locale);
+       else
+               tcu_cursor_fixture_set_locale (fixture, "en_US.UTF-8");
+
+       for (ii = 0; ii < N_SORTED_CONTACTS; ii++) {
+               gchar *case_name = g_strdup_printf ("sorted-%d", ii + 1);
+               gchar *vcard;
+               EContact *contact;
+
+               vcard = tcu_new_vcard_from_test_case (case_name);
+               contact = e_contact_new_from_vcard (vcard);
+               contacts = g_slist_prepend (contacts, contact);
+               extra_list = g_slist_prepend (extra_list, case_name);
+
+               g_free (vcard);
+
+               fixture->contacts[ii] = g_object_ref (contact);
+       }
+
+       if (!e_book_cache_put_contacts (base_fixture->book_cache, contacts, extra_list, FALSE, NULL, &error)) 
{
+               /* Dont complain here, we re-use the same addressbook for multiple tests
+                * and we can't add the same contacts twice
+                */
+               if (g_error_matches (error, E_CACHE_ERROR, E_CACHE_ERROR_CONSTRAINT))
+                       g_clear_error (&error);
+               else
+                       g_error ("Failed to add test contacts: %s", error->message);
+       }
+
+       g_slist_free_full (contacts, g_object_unref);
+       g_slist_free_full (extra_list, g_free);
+
+       /* Allow a surrounding fixture setup to add a query here */
+       if (fixture->query) {
+               sexp = e_book_query_to_string (fixture->query);
+               e_book_query_unref (fixture->query);
+               fixture->query = NULL;
+       }
+
+       fixture->cursor = e_book_cache_cursor_new (
+               base_fixture->book_cache, sexp,
+               sort_fields, sort_types, 2, &error);
+
+       if (!fixture->cursor)
+               g_error ("Failed to create cursor: %s\n", error->message);
+
+       g_free (sexp);
+}
+
+void
+tcu_cursor_fixture_filtered_setup (TCUCursorFixture *fixture,
+                                  gconstpointer user_data)
+{
+       fixture->query = e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_ENDS_WITH, ".com");
+
+       tcu_cursor_fixture_setup (fixture, user_data);
+}
+
+void
+tcu_cursor_fixture_teardown (TCUCursorFixture *fixture,
+                            gconstpointer user_data)
+{
+       TCUFixture *base_fixture = (TCUFixture   *) fixture;
+       gint ii;
+
+       for (ii = 0; ii < N_SORTED_CONTACTS; ii++) {
+               if (fixture->contacts[ii])
+                       g_object_unref (fixture->contacts[ii]);
+       }
+
+       e_book_cache_cursor_free (base_fixture->book_cache, fixture->cursor);
+       tcu_fixture_teardown (base_fixture, user_data);
+}
+
+void
+tcu_cursor_fixture_set_locale (TCUCursorFixture *fixture,
+                              const gchar *locale)
+{
+       TCUFixture *base_fixture = (TCUFixture   *) fixture;
+       GError *error = NULL;
+
+       if (!e_book_cache_set_locale (base_fixture->book_cache, locale, NULL, &error))
+               g_error ("Failed to set locale: %s", error->message);
+}
+
+static gint
+find_contact_data (EBookCacheSearchData *data,
+                   const gchar *uid)
+{
+       return g_strcmp0 (data->uid, uid);
+}
+
+void
+tcu_assert_contacts_order_slist (GSList *results,
+                                GSList *uids)
+{
+       gint position = -1;
+       GSList *link, *l;
+
+       /* Assert that all passed UIDs are found in the
+        * results, and that those UIDs are in the
+        * specified order.
+        */
+       for (l = uids; l; l = l->next) {
+               const gchar *uid = l->data;
+               gint new_position;
+
+               link = g_slist_find_custom (results, uid, (GCompareFunc) find_contact_data);
+               if (!link)
+                       g_error ("Specified uid '%s' was not found in results", uid);
+
+               new_position = g_slist_position (results, link);
+               g_assert_cmpint (new_position, >, position);
+               position = new_position;
+       }
+}
+
+void
+tcu_assert_contacts_order (GSList *results,
+                          const gchar *first_uid,
+                          ...)
+{
+       GSList *uids = NULL;
+       gchar *uid;
+       va_list args;
+
+       g_assert (first_uid);
+
+       uids = g_slist_append (uids, (gpointer) first_uid);
+
+       va_start (args, first_uid);
+       uid = va_arg (args, gchar *);
+       while (uid) {
+               uids = g_slist_append (uids, uid);
+               uid = va_arg (args, gchar *);
+       }
+       va_end (args);
+
+       tcu_assert_contacts_order_slist (results, uids);
+       g_slist_free (uids);
+}
+
+void
+tcu_print_results (const GSList *results)
+{
+       const GSList *link;
+
+       if (g_getenv ("TEST_DEBUG") == NULL)
+               return;
+
+       g_print ("\nPRINTING RESULTS:\n");
+
+       for (link = results; link; link = link->next) {
+               EBookCacheSearchData *data = link->data;
+
+               g_print ("\n%s\n", data->vcard);
+       }
+
+       g_print ("\nRESULT LIST_FINISHED\n");
+}
+
+/********************************************
+ *           Move By Test Helpers
+ ********************************************/
+#define DEBUG_FIXTURE        0
+
+static TCUStepData *
+step_test_new_internal (const gchar *test_path,
+                        const gchar *locale,
+                        gboolean empty_book)
+{
+       TCUStepData *data;
+
+       data = g_slice_new0 (TCUStepData);
+
+       data->parent.locale = g_strdup (locale);
+       data->parent.sort_type = E_BOOK_CURSOR_SORT_ASCENDING;
+
+       if (empty_book)
+               data->parent.parent.setup_summary = tcu_setup_empty_book;
+
+       data->path = g_strdup (test_path);
+
+       return data;
+}
+
+static void
+step_test_free (TCUStepData *data)
+{
+       GList *l;
+
+       g_free (data->path);
+       g_free ((gchar *) data->parent.locale);
+
+       for (l = data->assertions; l; l = l->next) {
+               TCUStepAssertion *assertion = l->data;
+
+               g_free (assertion->locale);
+               g_slice_free (TCUStepAssertion, assertion);
+       }
+
+       g_slice_free (TCUStepData, data);
+}
+
+TCUStepData *
+tcu_step_test_new (const gchar *test_prefix,
+                  const gchar *test_path,
+                  const gchar *locale,
+                  gboolean empty_book)
+{
+       TCUStepData *data;
+       gchar *path;
+
+       path = g_strconcat (test_prefix, test_path, NULL);
+       data = step_test_new_internal (path, locale, empty_book);
+       g_free (path);
+
+       return data;
+}
+
+TCUStepData *
+tcu_step_test_new_full (const gchar *test_prefix,
+                       const gchar *test_path,
+                       const gchar *locale,
+                       gboolean empty_book,
+                       EBookCursorSortType sort_type)
+{
+       TCUStepData *data;
+       gchar *path;
+
+       path = g_strconcat (test_prefix, test_path, NULL);
+       data = step_test_new_internal (path, locale, empty_book);
+       data->parent.sort_type = sort_type;
+       g_free (path);
+
+       return data;
+}
+
+static void
+test_cursor_move_teardown (TCUCursorFixture *fixture,
+                          gconstpointer user_data)
+{
+       TCUStepData *data = (TCUStepData *) user_data;
+
+       tcu_cursor_fixture_teardown (fixture, user_data);
+       step_test_free (data);
+}
+
+static void
+assert_step (TCUCursorFixture *fixture,
+            TCUStepData *data,
+            TCUStepAssertion *assertion,
+            GSList *results,
+            gint n_results,
+            gboolean expect_results)
+{
+       GSList *uids = NULL;
+       gint ii, expected = 0;
+
+       /* Count the number of really expected results */
+       for (ii = 0; ii < ABS (assertion->count); ii++) {
+               gint index = assertion->expected[ii];
+
+               if (index < 0)
+                       break;
+
+               expected++;
+       }
+
+       g_assert_cmpint (n_results, ==, expected);
+       if (!expect_results) {
+               g_assert_cmpint (g_slist_length (results), ==, 0);
+               return;
+       }
+
+       /* Assert the exact amount of requested results */
+       g_assert_cmpint (g_slist_length (results), ==, expected);
+
+#if DEBUG_FIXTURE
+       g_print (
+               "%s: Constructing expected result list for a fetch of %d: ",
+               data->path, assertion->count);
+#endif
+       for (ii = 0; ii < ABS (assertion->count); ii++) {
+               gint index = assertion->expected[ii];
+               gchar *uid;
+
+               if (index < 0)
+                       break;
+
+               uid = (gchar *) e_contact_get_const (fixture->contacts[index], E_CONTACT_UID);
+               uids = g_slist_append (uids, uid);
+
+#if DEBUG_FIXTURE
+               g_print ("%s ", uid);
+#endif
+
+       }
+#if DEBUG_FIXTURE
+       g_print ("\n");
+#endif
+
+       tcu_assert_contacts_order_slist (results, uids);
+       g_slist_free (uids);
+}
+
+static void
+test_step (TCUCursorFixture *fixture,
+          gconstpointer user_data)
+{
+       TCUFixture *base_fixture = (TCUFixture   *) fixture;
+       TCUStepData *data = (TCUStepData *) user_data;
+       GSList *results = NULL;
+       GError *error = NULL;
+       gint n_results;
+       EBookCacheCursorOrigin origin;
+       GList *l;
+       gboolean reset = TRUE;
+
+       for (l = data->assertions; l; l = l->next) {
+               TCUStepAssertion *assertion = l->data;
+
+               if (assertion->locale) {
+                       gint n_locale_changes = base_fixture->n_locale_changes;
+
+                       if (!e_book_cache_set_locale (base_fixture->book_cache, assertion->locale, NULL, 
&error))
+                               g_error ("Failed to set locale: %s", error->message);
+
+                       n_locale_changes = (base_fixture->n_locale_changes - n_locale_changes);
+
+                       /* Only check for contact changes is phone numbers are supported,
+                        * contact changes only happen because of e164 number interpretations.
+                        */
+                       if (e_phone_number_is_supported () &&
+                           assertion->count != n_locale_changes)
+                               g_error ("Expected %d e164 numbers to change, %d actually changed.",
+                                       assertion->count, n_locale_changes);
+
+                       reset = TRUE;
+                       continue;
+               }
+
+               /* For the first call to e_book_cache_cursor_step(),
+               * or the first reset after locale change, set the origin accordingly.
+                */
+              if (reset) {
+                      if (assertion->count < 0)
+                              origin = E_BOOK_CACHE_CURSOR_ORIGIN_END;
+                      else
+                              origin = E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN;
+
+                      reset = FALSE;
+              } else {
+                      origin = E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT;
+              }
+
+               /* Try only fetching the contacts but not moving the cursor */
+               n_results = e_book_cache_cursor_step (
+                       base_fixture->book_cache,
+                       fixture->cursor,
+                       E_BOOK_CACHE_CURSOR_STEP_FETCH,
+                       origin,
+                       assertion->count,
+                       &results,
+                       NULL, &error);
+               if (n_results < 0)
+                       g_error ("Error fetching cursor results: %s", error->message);
+
+               tcu_print_results (results);
+               assert_step (fixture, data, assertion, results, n_results, TRUE);
+               g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+               results = NULL;
+
+               /* Do it again, this time only moving the cursor */
+               n_results = e_book_cache_cursor_step (
+                       base_fixture->book_cache,
+                       fixture->cursor,
+                       E_BOOK_CACHE_CURSOR_STEP_MOVE,
+                       origin,
+                       assertion->count,
+                       &results,
+                       NULL, &error);
+               if (n_results < 0)
+                       g_error ("Error fetching cursor results: %s", error->message);
+
+               tcu_print_results (results);
+               assert_step (fixture, data, assertion, results, n_results, FALSE);
+               g_slist_free_full (results, (GDestroyNotify) e_book_cache_search_data_free);
+               results = NULL;
+       }
+}
+
+static void
+step_test_add_assertion_va_list (TCUStepData *data,
+                                gint count,
+                                va_list args)
+{
+       TCUStepAssertion *assertion = g_slice_new0 (TCUStepAssertion);
+       gint expected, ii = 0;
+
+       assertion->count = count;
+
+#if DEBUG_FIXTURE
+       g_print ("Adding assertion to test %d: %s\n", ii + 1, data->path);
+       g_print ("  Test will move by %d and expect: ", count);
+#endif
+       for (ii = 0; ii < ABS (count); ii++) {
+               expected = va_arg (args, gint);
+
+#if DEBUG_FIXTURE
+               g_print ("%d ", expected);
+#endif
+               assertion->expected[ii] = expected - 1;
+       }
+#if DEBUG_FIXTURE
+       g_print ("\n");
+#endif
+
+       data->assertions = g_list_append (data->assertions, assertion);
+}
+
+/* A positive of negative 'count' value
+ * followed by ABS (count) UID indexes.
+ *
+ * The indexes start at 1 so that they
+ * are easier to match up with the chart
+ * in data-test-utils.h
+ */
+void
+tcu_step_test_add_assertion (TCUStepData *data,
+                            gint count,
+                            ...)
+{
+       va_list args;
+
+       va_start (args, count);
+       step_test_add_assertion_va_list (data, count, args);
+       va_end (args);
+}
+
+void
+tcu_step_test_change_locale (TCUStepData *data,
+                            const gchar *locale,
+                            gint expected_changes)
+{
+       TCUStepAssertion *assertion = g_slice_new0 (TCUStepAssertion);
+
+       assertion->locale = g_strdup (locale);
+       assertion->count = expected_changes;
+       data->assertions = g_list_append (data->assertions, assertion);
+}
+
+void
+tcu_step_test_add (TCUStepData *data,
+                  gboolean filtered)
+{
+       data->filtered = filtered;
+
+       g_test_add (
+               data->path, TCUCursorFixture, data,
+               filtered ?
+               tcu_cursor_fixture_filtered_setup :
+               tcu_cursor_fixture_setup,
+               test_step,
+               test_cursor_move_teardown);
+}
diff --git a/tests/libedata-book/test-cache-utils.h b/tests/libedata-book/test-cache-utils.h
new file mode 100644
index 0000000..4b379c0
--- /dev/null
+++ b/tests/libedata-book/test-cache-utils.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013, Openismus GmbH
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Tristan Van Berkom <tristanvb openismus com>
+ */
+
+#ifndef TEST_CACHE_UTILS_H
+#define TEST_CACHE_UTILS_H
+
+#include <libedata-book/libedata-book.h>
+
+/* This legend shows the add order, and various sort order of the sorted
+ * vcards. The UIDs of these contacts are formed as 'sorted-1', 'sorted-2' etc
+ * and the numbering of the contacts is according to the 'N' column in the
+ * following legend.
+ *
+ * The Email column indicates whether the contact has a .com email address
+ * (in order to test filtered cursor results) and corresponds to the natural
+ * order in the 'N' column.
+ *
+ * +-----------------------------------------------------------------------------------------------+
+ * | N   | Email | Last Name   | en_US_POSIX    | en_US / de_DE  | fr_CA          | de_DE          |
+ * |     |       |             |                |                |                | (phonebook)    |
+ * +-----------------------------------------------------------------------------------------------+
+ * | 1   | Yes   | bad         |             11 |             11 |             11 |             11 |
+ * | 2   | Yes   | Bad         | Bad         2  | bad         1  | bad         1  | bad         1  |
+ * | 3   | Yes   | Bat         | Bäd         6  | Bad         2  | Bad         2  | Bad         2  |
+ * | 4   | No    | bat         | Bat         3  | bäd         5  | bäd         5  | bäd         5  |
+ * | 5   | Yes   | bäd         | Bät         8  | Bäd         6  | Bäd         6  | Bäd         6  |
+ * | 6   | No    | Bäd         | bad         1  | bat         4  | bat         4  | bät         7  |
+ * | 7   | No    | bät         | bäd         5  | Bat         3  | Bat         3  | Bät         8  |
+ * | 8   | Yes   | Bät         | bat         4  | bät         7  | bät         7  | bat         4  |
+ * | 9   | Yes   | côté        | bät         7  | Bät         8  | Bät         8  | Bat         3  |
+ * | 10  | Yes   | C           | black-bird  15 | black-bird  15 | black-bird  15 | black-bird  15 |
+ * | 11  | Yes   |             | black-birds 17 | black-birds 17 | black-birds 17 | black-birds 17 |
+ * | 12  | Yes   | coté        | blackbird   16 | blackbird   16 | blackbird   16 | blackbird   16 |
+ * | 13  | No    | côte        | blackbirds  18 | blackbirds  18 | blackbirds  18 | blackbirds  18 |
+ * | 14  | Yes   | cote        | C           10 | C           10 | C           10 | C           10 |
+ * | 15  | No    | black-bird  | cote        14 | cote        14 | cote        14 | cote        14 |
+ * | 16  | Yes   | blackbird   | coté        12 | coté        12 | côte        13 | coté        12 |
+ * | 17  | Yes   | black-birds | côte        13 | côte        13 | coté        12 | côte        13 |
+ * | 18  | Yes   | blackbirds  | côté        9  | côté        9  | côté        9  | côté        9  |
+ * | 19  | No    | Muffler     | Muffler     19 | Muffler     19 | Muffler     19 | Müller      20 |
+ * | 20  | No    | Müller      | Müller      20 | Müller      20 | Müller      20 | Muffler     19 |
+ * +-----------------------------------------------------------------------------------------------+
+ *
+ * See this ICU demo to check additional sort ordering by ICU in various locales:
+ *     http://demo.icu-project.org/icu-bin/locexp?_=en_US&d_=en&x=col
+ */
+
+/* 13 contacts in the test data have an email address ending with ".com" */
+#define N_FILTERED_CONTACTS  13
+#define N_SORTED_CONTACTS    20
+
+typedef ESourceBackendSummarySetup * (* TCUSetupSummaryFunc) (void);
+
+typedef struct {
+       EBookCache *book_cache;
+
+       gint n_add_changes;
+       gint n_locale_changes;
+} TCUFixture;
+
+typedef struct {
+       TCUSetupSummaryFunc setup_summary;
+} TCUClosure;
+
+typedef struct {
+       TCUFixture parent_fixture;
+
+       EBookCacheCursor *cursor;
+       EContact *contacts[N_SORTED_CONTACTS];
+       EBookQuery *query;
+
+       guint own_id;
+} TCUCursorFixture;
+
+typedef struct {
+       TCUClosure parent;
+
+       const gchar *locale;
+       EBookCursorSortType sort_type;
+} TCUCursorClosure;
+
+typedef struct {
+       /* A locale change */
+       gchar *locale;
+
+       /* count argument for move */
+       gint count;
+
+       /* An array of 'ABS (counts[i])' expected contacts */
+       gint expected[N_SORTED_CONTACTS];
+} TCUStepAssertion;
+
+typedef struct {
+       TCUCursorClosure parent;
+       gchar *path;
+
+       GList *assertions;
+
+       /* Whether this is a filtered test */
+       gboolean filtered;
+} TCUStepData;
+
+/* Base fixture */
+void           tcu_fixture_setup                       (TCUFixture *fixture,
+                                                        gconstpointer user_data);
+void           tcu_fixture_teardown                    (TCUFixture *fixture,
+                                                        gconstpointer user_data);
+ESourceBackendSummarySetup *
+               tcu_setup_empty_book                    (void);
+
+/* Cursor fixture */
+void           tcu_cursor_fixture_setup                (TCUCursorFixture *fixture,
+                                                        gconstpointer user_data);
+void           tcu_cursor_fixture_teardown             (TCUCursorFixture *fixture,
+                                                        gconstpointer user_data);
+void           tcu_cursor_fixture_set_locale           (TCUCursorFixture *fixture,
+                                                        const gchar *locale);
+
+/* Filters contacts with E_CONTACT_EMAIL ending with '.com' */
+void           tcu_cursor_fixture_filtered_setup       (TCUCursorFixture *fixture,
+                                                        gconstpointer user_data);
+
+gchar *                tcu_new_vcard_from_test_case            (const gchar *case_name);
+EContact *     tcu_new_contact_from_test_case          (const gchar *case_name);
+
+void           tcu_add_contact_from_test_case          (TCUFixture *fixture,
+                                                        const gchar *case_name,
+                                                        EContact **ret_contact);
+void           tcu_assert_contacts_order_slist         (GSList *results,
+                                                        GSList *uids);
+void           tcu_assert_contacts_order               (GSList *results,
+                                                        const gchar *first_uid,
+                                                        ...) G_GNUC_NULL_TERMINATED;
+
+void           tcu_print_results                       (const GSList *results);
+
+/*  Step test helpers */
+void           tcu_step_test_add_assertion             (TCUStepData *data,
+                                                        gint count,
+                                                        ...);
+void           tcu_step_test_change_locale             (TCUStepData *data,
+                                                        const gchar *locale,
+                                                        gint expected_changes);
+
+TCUStepData *  tcu_step_test_new                       (const gchar *test_prefix,
+                                                        const gchar *test_path,
+                                                        const gchar *locale,
+                                                        gboolean empty_book);
+TCUStepData *  tcu_step_test_new_full                  (const gchar *test_prefix,
+                                                        const gchar *test_path,
+                                                        const gchar *locale,
+                                                        gboolean empty_book,
+                                                        EBookCursorSortType sort_type);
+
+void           tcu_step_test_add                       (TCUStepData *data,
+                                                        gboolean filtered);
+
+#endif /* TEST_CACHE_UTILS_H */


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