[evolution-data-server/wip/offline-cache] Add more ECalMetaBackend tests



commit d81267fd1285828fef9c504180389c2da1ba3ce6
Author: Milan Crha <mcrha redhat com>
Date:   Thu Mar 9 15:20:22 2017 +0100

    Add more ECalMetaBackend tests

 src/calendar/libedata-cal/e-cal-meta-backend.c |   97 +-
 src/libebackend/e-backend.c                    |   13 +-
 src/libebackend/e-cache.h                      |   10 +-
 tests/libedata-cal/components/event-1.ics      |   36 +-
 tests/libedata-cal/components/event-2.ics      |   28 +-
 tests/libedata-cal/components/event-3.ics      |   24 +-
 tests/libedata-cal/components/event-4.ics      |   24 +-
 tests/libedata-cal/components/event-5.ics      |   36 +-
 tests/libedata-cal/components/event-6-a.ics    |   26 +-
 tests/libedata-cal/components/event-6.ics      |   24 +-
 tests/libedata-cal/components/event-7.ics      |   28 +-
 tests/libedata-cal/components/event-8.ics      |   32 +-
 tests/libedata-cal/components/event-9.ics      |   34 +-
 tests/libedata-cal/components/invite-1.ics     |   19 +
 tests/libedata-cal/components/invite-2.ics     |   19 +
 tests/libedata-cal/components/invite-3.ics     |   21 +
 tests/libedata-cal/components/invite-4.ics     |   21 +
 tests/libedata-cal/test-cal-meta-backend.c     | 2018 +++++++++++++++++++++++-
 18 files changed, 2336 insertions(+), 174 deletions(-)
---
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.c b/src/calendar/libedata-cal/e-cal-meta-backend.c
index 37a0472..de167dc 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.c
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.c
@@ -68,6 +68,13 @@ enum {
        PROP_CACHE
 };
 
+enum {
+       REFRESH_COMPLETED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
 G_DEFINE_ABSTRACT_TYPE (ECalMetaBackend, e_cal_meta_backend, E_TYPE_CAL_BACKEND_SYNC)
 
 G_DEFINE_BOXED_TYPE (ECalMetaBackendInfo, e_cal_meta_backend_info, e_cal_meta_backend_info_copy, 
e_cal_meta_backend_info_free)
@@ -384,6 +391,17 @@ ecmb_get_changes_sync (ECalMetaBackend *meta_backend,
                        continue;
                }
 
+               /* Skit detached instances, if the master object is still in the cache */
+               if (id->rid && *id->rid) {
+                       ECalComponentId master_id;
+
+                       master_id.uid = id->uid;
+                       master_id.rid = NULL;
+
+                       if (!g_hash_table_contains (locally_cached, &master_id))
+                               continue;
+               }
+
                nfo = e_cal_meta_backend_info_new (id->uid, id->rid, revision);
                *out_removed_objects = g_slist_prepend (*out_removed_objects, nfo);
        }
@@ -527,20 +545,19 @@ ecmb_refresh_thread_func (ECalBackend *cal_backend,
        g_return_if_fail (E_IS_CAL_META_BACKEND (cal_backend));
 
        if (g_cancellable_set_error_if_cancelled (cancellable, error))
-               return;
+               goto done;
 
        meta_backend = E_CAL_META_BACKEND (cal_backend);
 
        if (!e_backend_get_online (E_BACKEND (meta_backend)) ||
            !ecmb_connect_wrapper_sync (meta_backend, cancellable, NULL)) {
                /* Ignore connection errors here */
-               return;
+               goto done;
        }
 
        cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
-       if (!cal_cache) {
-               return;
-       }
+       if (!cal_cache)
+               goto done;
 
        success = ecmb_upload_local_changes_sync (meta_backend, cal_cache, E_CONFLICT_RESOLUTION_KEEP_LOCAL, 
cancellable, error);
 
@@ -629,6 +646,16 @@ ecmb_refresh_thread_func (ECalBackend *cal_backend,
        }
 
        g_object_unref (cal_cache);
+
+ done:
+       g_mutex_lock (&meta_backend->priv->property_lock);
+
+       if (meta_backend->priv->refresh_cancellable == cancellable)
+               g_clear_object (&meta_backend->priv->refresh_cancellable);
+
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
+       g_signal_emit (cal_backend, signals[REFRESH_COMPLETED], 0, NULL);
 }
 
 static void
@@ -1214,7 +1241,7 @@ ecmb_get_free_busy_sync (ECalBackendSync *sync_backend,
        ECalMetaBackend *meta_backend;
        ECalCache *cal_cache;
        GSList *link, *components = NULL;
-       gchar *cal_email_address;
+       gchar *cal_email_address, *mailto;
        icalcomponent *vfreebusy, *icalcomp;
        icalproperty *prop;
        icaltimezone *utc_zone;
@@ -1247,15 +1274,19 @@ ecmb_get_free_busy_sync (ECalBackendSync *sync_backend,
 
        cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
 
-       if (!e_cal_cache_get_components_in_range (cal_cache, start, end, &components, cancellable, error)) {
+       if (!cal_cache || !e_cal_cache_get_components_in_range (cal_cache, start, end, &components, 
cancellable, error)) {
                g_clear_object (&cal_cache);
                g_free (cal_email_address);
                return;
        }
 
        vfreebusy = icalcomponent_new_vfreebusy ();
-       prop = icalproperty_new_organizer (cal_email_address);
-       if (prop != NULL)
+
+       mailto = g_strconcat ("mailto:";, cal_email_address, NULL);
+       prop = icalproperty_new_organizer (mailto);
+       g_free (mailto);
+
+       if (prop)
                icalcomponent_add_property (vfreebusy, prop);
 
        utc_zone = icaltimezone_get_utc_timezone ();
@@ -1297,6 +1328,7 @@ ecmb_get_free_busy_sync (ECalBackendSync *sync_backend,
 
        g_slist_free_full (components, g_object_unref);
        icalcomponent_free (vfreebusy);
+       g_object_unref (cal_cache);
        g_free (cal_email_address);
 }
 
@@ -1505,6 +1537,15 @@ ecmb_modify_object_sync (ECalMetaBackend *meta_backend,
        if (e_cal_component_is_instance (comp)) {
                /* Set detached instance as the old object */
                existing_comp = ecmb_find_in_instances (instances, id->uid, id->rid);
+
+               if (!existing_comp && mod == E_CAL_OBJ_MOD_ONLY_THIS) {
+                       g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+
+                       g_slist_free_full (instances, g_object_unref);
+                       e_cal_component_free_id (id);
+
+                       return FALSE;
+               }
        }
 
        if (!existing_comp)
@@ -2317,6 +2358,25 @@ ecmb_get_attachment_uris_sync (ECalBackendSync *sync_backend,
 }
 
 static void
+ecmb_discard_alarm_sync (ECalBackendSync *sync_backend,
+                        EDataCal *cal,
+                        GCancellable *cancellable,
+                        const gchar *uid,
+                        const gchar *rid,
+                        const gchar *auid,
+                        GError **error)
+{
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (uid != NULL);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED,
+               e_client_error_to_string (E_CLIENT_ERROR_NOT_SUPPORTED));
+}
+
+static void
 ecmb_get_timezone_sync (ECalBackendSync *sync_backend,
                        EDataCal *cal,
                        GCancellable *cancellable,
@@ -2360,7 +2420,8 @@ ecmb_get_timezone_sync (ECalBackendSync *sync_backend,
 
        *tzobject = timezone_str;
 
-       g_propagate_error (error, local_error);
+       if (local_error)
+               g_propagate_error (error, local_error);
 }
 
 static void
@@ -2799,6 +2860,7 @@ e_cal_meta_backend_class_init (ECalMetaBackendClass *klass)
        cal_backend_sync_class->receive_objects_sync = ecmb_receive_objects_sync;
        cal_backend_sync_class->send_objects_sync = ecmb_send_objects_sync;
        cal_backend_sync_class->get_attachment_uris_sync = ecmb_get_attachment_uris_sync;
+       cal_backend_sync_class->discard_alarm_sync = ecmb_discard_alarm_sync;
        cal_backend_sync_class->get_timezone_sync = ecmb_get_timezone_sync;
        cal_backend_sync_class->add_timezone_sync = ecmb_add_timezone_sync;
 
@@ -2832,6 +2894,16 @@ e_cal_meta_backend_class_init (ECalMetaBackendClass *klass)
                        E_TYPE_CAL_CACHE,
                        G_PARAM_READWRITE |
                        G_PARAM_STATIC_STRINGS));
+
+       /* This signal is meant for testing purposes mainly */
+       signals[REFRESH_COMPLETED] = g_signal_new (
+               "refresh-completed",
+               G_OBJECT_CLASS_TYPE (klass),
+               G_SIGNAL_RUN_LAST,
+               0,
+               NULL, NULL,
+               NULL,
+               G_TYPE_NONE, 0, G_TYPE_NONE);
 }
 
 static void
@@ -2861,7 +2933,10 @@ e_cal_meta_backend_get_capabilities (ECalMetaBackend *meta_backend)
 {
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), NULL);
 
-       return CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED;
+       return CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED ","
+               CAL_STATIC_CAPABILITY_BULK_ADDS ","
+               CAL_STATIC_CAPABILITY_BULK_MODIFIES ","
+               CAL_STATIC_CAPABILITY_BULK_REMOVES;
 }
 
 /**
diff --git a/src/libebackend/e-backend.c b/src/libebackend/e-backend.c
index 0e17755..5c35f50 100644
--- a/src/libebackend/e-backend.c
+++ b/src/libebackend/e-backend.c
@@ -133,7 +133,10 @@ backend_update_online_state_timeout_cb (gpointer user_data)
        if (current_source && g_source_is_destroyed (current_source))
                return FALSE;
 
-       backend = E_BACKEND (user_data);
+       backend = g_weak_ref_get (user_data);
+       if (!backend)
+               return FALSE;
+
        connectable = e_backend_ref_connectable (backend);
 
        g_mutex_lock (&backend->priv->update_online_state_lock);
@@ -180,8 +183,8 @@ backend_update_online_state_timeout_cb (gpointer user_data)
                g_mutex_unlock (&backend->priv->network_monitor_cancellable_lock);
        }
 
-       if (connectable != NULL)
-               g_object_unref (connectable);
+       g_clear_object (&connectable);
+       g_clear_object (&backend);
 
        return FALSE;
 }
@@ -211,7 +214,7 @@ backend_update_online_state (EBackend *backend)
        g_source_set_callback (
                timeout_source,
                backend_update_online_state_timeout_cb,
-               backend, (GDestroyNotify) g_object_unref);
+               e_weak_ref_new (backend), (GDestroyNotify) e_weak_ref_free);
        g_source_attach (timeout_source, main_context);
        backend->priv->update_online_state =
                g_source_ref (timeout_source);
@@ -220,6 +223,8 @@ backend_update_online_state (EBackend *backend)
        g_main_context_unref (main_context);
 
        g_mutex_unlock (&backend->priv->update_online_state_lock);
+
+       g_object_unref (backend);
 }
 
 static void
diff --git a/src/libebackend/e-cache.h b/src/libebackend/e-cache.h
index ca10faf..c8dac06 100644
--- a/src/libebackend/e-cache.h
+++ b/src/libebackend/e-cache.h
@@ -352,7 +352,7 @@ struct _ECacheClass {
                                                 GCancellable *cancellable,
                                                 GError **error);
        gboolean        (* remove_all_locked)   (ECache *cache,
-                                                const GSList *uids,
+                                                const GSList *uids, /* gchar * */
                                                 GCancellable *cancellable,
                                                 GError **error);
        gboolean        (* clear_offline_changes_locked)
@@ -425,14 +425,14 @@ guint             e_cache_get_count               (ECache *cache,
                                                 GError **error);
 gboolean       e_cache_get_uids                (ECache *cache,
                                                 ECacheDeletedFlag deleted_flag,
-                                                GSList **out_uids,
-                                                GSList **out_revisions,
+                                                GSList **out_uids, /* gchar * */
+                                                GSList **out_revisions, /* gchar * */
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_cache_get_objects             (ECache *cache,
                                                 ECacheDeletedFlag deleted_flag,
-                                                GSList **out_objects,
-                                                GSList **out_revisions,
+                                                GSList **out_objects, /* gchar * */
+                                                GSList **out_revisions, /* gchar * */
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_cache_foreach                 (ECache *cache,
diff --git a/tests/libedata-cal/components/event-1.ics b/tests/libedata-cal/components/event-1.ics
index 39eaa58..bbfb045 100644
--- a/tests/libedata-cal/components/event-1.ics
+++ b/tests/libedata-cal/components/event-1.ics
@@ -1,18 +1,18 @@
-BEGIN:VEVENT
-UID:event-1
-DTSTAMP:20170130T000000Z
-CREATED:20170216T155507Z
-LAST-MODIFIED:20170216T155543Z
-SEQUENCE:1
-DTSTART:20170209T013000Z
-DTEND:20170209T030000Z
-SUMMARY:Alarmed
-DESCRIPTION:Event with alarm
-CLASS:PUBLIC
-TRANSP:OPACHE
-BEGIN:VALARM
-TRIGGER:-PT30M
-ACTION:DISPLAY
-DESCRIPTION:Reminder
-END:VALARM
-END:VEVENT
+BEGIN:VEVENT
+UID:event-1
+DTSTAMP:20170130T000000Z
+CREATED:20170216T155507Z
+LAST-MODIFIED:20170216T155543Z
+SEQUENCE:1
+DTSTART:20170209T013000Z
+DTEND:20170209T030000Z
+SUMMARY:Alarmed
+DESCRIPTION:Event with alarm
+CLASS:PUBLIC
+TRANSP:OPACHE
+BEGIN:VALARM
+TRIGGER:-PT30M
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+END:VALARM
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-2.ics b/tests/libedata-cal/components/event-2.ics
index ce980be..8685802 100644
--- a/tests/libedata-cal/components/event-2.ics
+++ b/tests/libedata-cal/components/event-2.ics
@@ -1,14 +1,14 @@
-BEGIN:VEVENT
-UID:event-2
-DTSTAMP:20170103T070000Z
-CREATED:20170216T155507Z
-LAST-MODIFIED:20170216T155543Z
-SEQUENCE:1
-DTSTART:20170103T080000Z
-DTEND:20170103T083000Z
-SUMMARY:First working day
-DESCRIPTION:Multiline\n
- description text
-CLASS:CONFIDENTIAL
-CATEGORIES:Work,Hard
-END:VEVENT
+BEGIN:VEVENT
+UID:event-2
+DTSTAMP:20170103T070000Z
+CREATED:20170216T155507Z
+LAST-MODIFIED:20170216T155543Z
+SEQUENCE:1
+DTSTART:20170103T080000Z
+DTEND:20170103T083000Z
+SUMMARY:First working day
+DESCRIPTION:Multiline\n
+ description text
+CLASS:CONFIDENTIAL
+CATEGORIES:Work,Hard
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-3.ics b/tests/libedata-cal/components/event-3.ics
index 4fcf5bf..ed5126d 100644
--- a/tests/libedata-cal/components/event-3.ics
+++ b/tests/libedata-cal/components/event-3.ics
@@ -1,12 +1,12 @@
-BEGIN:VEVENT
-UID:event-3
-DTSTAMP:20170103T070000Z
-CREATED:20170216T155507Z
-LAST-MODIFIED:20170216T155543Z
-SEQUENCE:3
-DTSTART:20170104T100000Z
-DTEND:20170110T120000Z
-SUMMARY:Lunch prepare
-LOCATION:Kitchen
-CLASS:PRIVATE
-END:VEVENT
+BEGIN:VEVENT
+UID:event-3
+DTSTAMP:20170103T070000Z
+CREATED:20170216T155507Z
+LAST-MODIFIED:20170216T155543Z
+SEQUENCE:3
+DTSTART:20170104T100000Z
+DTEND:20170110T120000Z
+SUMMARY:Lunch prepare
+LOCATION:Kitchen
+CLASS:PRIVATE
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-4.ics b/tests/libedata-cal/components/event-4.ics
index 2fde425..e6641c9 100644
--- a/tests/libedata-cal/components/event-4.ics
+++ b/tests/libedata-cal/components/event-4.ics
@@ -1,12 +1,12 @@
-BEGIN:VEVENT
-UID:event-4
-DTSTAMP:20170102T000000Z
-CREATED:20170216T155507Z
-LAST-MODIFIED:20170216T155543Z
-SEQUENCE:3
-DTSTART:20170102T100000Z
-DTEND:20170102T180000Z
-SUMMARY:After-party clean up
-LOCATION:All around
-CLASS:PUBLIC
-END:VEVENT
+BEGIN:VEVENT
+UID:event-4
+DTSTAMP:20170102T000000Z
+CREATED:20170216T155507Z
+LAST-MODIFIED:20170216T155543Z
+SEQUENCE:3
+DTSTART:20170102T100000Z
+DTEND:20170102T180000Z
+SUMMARY:After-party clean up
+LOCATION:All around
+CLASS:PUBLIC
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-5.ics b/tests/libedata-cal/components/event-5.ics
index a12a488..7da5be4 100644
--- a/tests/libedata-cal/components/event-5.ics
+++ b/tests/libedata-cal/components/event-5.ics
@@ -1,18 +1,18 @@
-BEGIN:VEVENT
-UID:event-5
-DTSTAMP:20101231T000000Z
-CREATED:20170216T155507Z
-LAST-MODIFIED:20170216T155543Z
-SEQUENCE:1
-DTSTART:20091231T000000Z
-DTEND:20100101T235959Z
-SUMMARY:New Year Party
-LOCATION:All around
-CATEGORIES:Holiday,International
-RRULE:FREQ=YEARLY
-BEGIN:VALARM
-ACTION:DISPLAY
-TRIGGER;RELATED=START:-P1D
-DESCRIPTION:New Year Party
-END:VALARM
-END:VEVENT
+BEGIN:VEVENT
+UID:event-5
+DTSTAMP:20101231T000000Z
+CREATED:20170216T155507Z
+LAST-MODIFIED:20170216T155543Z
+SEQUENCE:1
+DTSTART:20091231T000000Z
+DTEND:20100101T235959Z
+SUMMARY:New Year Party
+LOCATION:All around
+CATEGORIES:Holiday,International
+RRULE:FREQ=YEARLY
+BEGIN:VALARM
+ACTION:DISPLAY
+TRIGGER;RELATED=START:-P1D
+DESCRIPTION:New Year Party
+END:VALARM
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-6-a.ics b/tests/libedata-cal/components/event-6-a.ics
index 404b51e..29752f6 100644
--- a/tests/libedata-cal/components/event-6-a.ics
+++ b/tests/libedata-cal/components/event-6-a.ics
@@ -1,13 +1,13 @@
-BEGIN:VEVENT
-UID:event-6
-DTSTAMP:20170221T121736Z
-DTSTART;TZID=America/New_York:20170225T150000
-DTEND;TZID=America/New_York:20170225T160000
-SEQUENCE:2
-SUMMARY:Recurring with detached instance (3rd instance)
-TRANSP:OPAQUE
-CLASS:PUBLIC
-CREATED:20170221T125024Z
-LAST-MODIFIED:20170221T125341Z
-RECURRENCE-ID;TZID=America/New_York:20170225T134900
-END:VEVENT
+BEGIN:VEVENT
+UID:event-6
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/New_York:20170225T150000
+DTEND;TZID=America/New_York:20170225T160000
+SEQUENCE:2
+SUMMARY:Recurring with detached instance (3rd instance)
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170221T125024Z
+LAST-MODIFIED:20170221T125341Z
+RECURRENCE-ID;TZID=America/New_York:20170225T134900
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-6.ics b/tests/libedata-cal/components/event-6.ics
index ee85a01..81d054d 100644
--- a/tests/libedata-cal/components/event-6.ics
+++ b/tests/libedata-cal/components/event-6.ics
@@ -1,12 +1,12 @@
-BEGIN:VEVENT
-UID:event-6
-DTSTAMP:20170221T121736Z
-DTSTART;TZID=America/New_York:20170221T134900
-DTEND;TZID=America/New_York:20170221T144900
-SEQUENCE:1
-SUMMARY:Recurring with detached instance
-RRULE:FREQ=DAILY;COUNT=5;INTERVAL=2
-CLASS:PUBLIC
-CREATED:20170221T125024Z
-LAST-MODIFIED:20170221T125024Z
-END:VEVENT
+BEGIN:VEVENT
+UID:event-6
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/New_York:20170221T134900
+DTEND;TZID=America/New_York:20170221T144900
+SEQUENCE:1
+SUMMARY:Recurring with detached instance
+RRULE:FREQ=DAILY;COUNT=5;INTERVAL=2
+CLASS:PUBLIC
+CREATED:20170221T125024Z
+LAST-MODIFIED:20170221T125024Z
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-7.ics b/tests/libedata-cal/components/event-7.ics
index b08d392..36bc5bd 100644
--- a/tests/libedata-cal/components/event-7.ics
+++ b/tests/libedata-cal/components/event-7.ics
@@ -1,14 +1,14 @@
-BEGIN:VEVENT
-UID:event-7
-DTSTAMP:20170221T121736Z
-DTSTART;TZID=/freeassociation.sourceforge.net/America/New_York:20170221T135000
-DTEND;TZID=/freeassociation.sourceforge.net/America/New_York:20170221T145000
-SEQUENCE:1
-SUMMARY:With attachment
-TRANSP:OPAQUE
-ATTACH:file:///usr/share/icons/hicolor/48x48/apps/evolution.png
-CLASS:PUBLIC
-CREATED:20170221T125054Z
-LAST-MODIFIED:20170221T125054Z
-CATEGORIES:Holiday,Work
-END:VEVENT
+BEGIN:VEVENT
+UID:event-7
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=/freeassociation.sourceforge.net/America/New_York:20170221T135000
+DTEND;TZID=/freeassociation.sourceforge.net/America/New_York:20170221T145000
+SEQUENCE:1
+SUMMARY:With attachment
+TRANSP:OPAQUE
+ATTACH:file:///usr/share/icons/hicolor/48x48/apps/evolution.png
+CLASS:PUBLIC
+CREATED:20170221T125054Z
+LAST-MODIFIED:20170221T125054Z
+CATEGORIES:Holiday,Work
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-8.ics b/tests/libedata-cal/components/event-8.ics
index 35887ad..8818d9f 100644
--- a/tests/libedata-cal/components/event-8.ics
+++ b/tests/libedata-cal/components/event-8.ics
@@ -1,16 +1,16 @@
-BEGIN:VEVENT
-UID:event-8
-DTSTAMP:20170221T121736Z
-DTSTART:20170225T160000
-DTEND:20170225T160500
-SEQUENCE:2
-ORGANIZER;CN=Bob:MAILTO:bob@no.where
-ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
- RSVP=TRUE;LANGUAGE=en:MAILTO:alice@no.where
-SUMMARY:Meet Alice (Floating time)
-COMMENT:User commentary text
-TRANSP:OPAQUE
-CLASS:PUBLIC
-CREATED:20170221T131322Z
-LAST-MODIFIED:20170221T131322Z
-END:VEVENT
+BEGIN:VEVENT
+UID:event-8
+DTSTAMP:20170221T121736Z
+DTSTART:20170225T160000
+DTEND:20170225T160500
+SEQUENCE:2
+ORGANIZER;CN=Bob:MAILTO:bob@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
+ RSVP=TRUE;LANGUAGE=en:MAILTO:alice@no.where
+SUMMARY:Meet Alice (Floating time)
+COMMENT:User commentary text
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170221T131322Z
+LAST-MODIFIED:20170221T131322Z
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-9.ics b/tests/libedata-cal/components/event-9.ics
index 70b96f7..7051c24 100644
--- a/tests/libedata-cal/components/event-9.ics
+++ b/tests/libedata-cal/components/event-9.ics
@@ -1,17 +1,17 @@
-BEGIN:VEVENT
-UID:event-9
-DTSTAMP:20170221T121736Z
-DTSTART;TZID=America/new_York:20170225T160000
-DTEND;TZID=America/new_York:20170225T170000
-SEQUENCE:2
-ORGANIZER;CN=Alice:MAILTO:alice@no.where
-ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
- RSVP=TRUE;CN=Bob;LANGUAGE=en:MAILTO:bob@no.where
-ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
- RSVP=TRUE;CN=Charlie;LANGUAGE=en:MAILTO:charlie@no.where
-SUMMARY:2-on-1
-TRANSP:OPAQUE
-CLASS:PUBLIC
-CREATED:20170221T131421Z
-LAST-MODIFIED:20170221T131421Z
-END:VEVENT
+BEGIN:VEVENT
+UID:event-9
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/new_York:20170225T160000
+DTEND;TZID=America/new_York:20170225T170000
+SEQUENCE:2
+ORGANIZER;CN=Alice:MAILTO:alice@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
+ RSVP=TRUE;CN=Bob;LANGUAGE=en:MAILTO:bob@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
+ RSVP=TRUE;CN=Charlie;LANGUAGE=en:MAILTO:charlie@no.where
+SUMMARY:2-on-1
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170221T131421Z
+LAST-MODIFIED:20170221T131421Z
+END:VEVENT
diff --git a/tests/libedata-cal/components/invite-1.ics b/tests/libedata-cal/components/invite-1.ics
new file mode 100644
index 0000000..8f04da2
--- /dev/null
+++ b/tests/libedata-cal/components/invite-1.ics
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:invite
+DTSTAMP:20170308T175957Z
+DTSTART:20170321T120000Z
+DTEND:20170321T130000Z
+SEQUENCE:1
+ORGANIZER;CN=Organizer:MAILTO:organizer@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
+ RSVP=TRUE;CN=User;LANGUAGE=en:MAILTO:user@no.where
+SUMMARY:Invite
+TRANSP:OPAQUE
+CLASS:PUBLIC
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/libedata-cal/components/invite-2.ics b/tests/libedata-cal/components/invite-2.ics
new file mode 100644
index 0000000..4261ec4
--- /dev/null
+++ b/tests/libedata-cal/components/invite-2.ics
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+METHOD:REPLY
+BEGIN:VEVENT
+UID:invite
+DTSTAMP:20170308T181958Z
+DTSTART:20170321T120000Z
+DTEND:20170321T130000Z
+SEQUENCE:1
+ORGANIZER;CN=Organizer:MAILTO:organizer@no.where
+ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=ACCEPTED:MAILTO:user@no.where
+SUMMARY:Invite
+TRANSP:OPAQUE
+CLASS:PUBLIC
+COMMENT:See you there
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/libedata-cal/components/invite-3.ics b/tests/libedata-cal/components/invite-3.ics
new file mode 100644
index 0000000..34887f5
--- /dev/null
+++ b/tests/libedata-cal/components/invite-3.ics
@@ -0,0 +1,21 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:invite
+DTSTAMP:20170308T180058Z
+DTSTART:20170321T130000Z
+DTEND:20170321T140000Z
+SEQUENCE:2
+ORGANIZER;CN=Organizer:MAILTO:organizer@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
+ RSVP=TRUE;CN=User;LANGUAGE=en:MAILTO:user@no.where
+SUMMARY:Invite (modified)
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170308T180033Z
+LAST-MODIFIED:20170308T180033Z
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/libedata-cal/components/invite-4.ics b/tests/libedata-cal/components/invite-4.ics
new file mode 100644
index 0000000..38fd977
--- /dev/null
+++ b/tests/libedata-cal/components/invite-4.ics
@@ -0,0 +1,21 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Ximian//NONSGML Evolution Calendar//EN
+VERSION:2.0
+METHOD:CANCEL
+BEGIN:VEVENT
+UID:invite
+DTSTAMP:20170308T180113Z
+DTSTART:20170321T130000Z
+DTEND:20170321T140000Z
+SEQUENCE:2
+ORGANIZER;CN=Organizer:MAILTO:organizer@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
+ RSVP=TRUE;CN=User;LANGUAGE=en:MAILTO:user@no.where
+SUMMARY:Invite (modified)
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170308T180033Z
+LAST-MODIFIED:20170308T180058Z
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/libedata-cal/test-cal-meta-backend.c b/tests/libedata-cal/test-cal-meta-backend.c
index 7d0c910..02eb5d8 100644
--- a/tests/libedata-cal/test-cal-meta-backend.c
+++ b/tests/libedata-cal/test-cal-meta-backend.c
@@ -27,9 +27,21 @@
 #define EXPECTED_TZID          "/freeassociation.sourceforge.net/America/New_York"
 #define EXPECTED_LOCATION      "America/New_York"
 #define REMOTE_URL             "https://www.gnome.org/wp-content/themes/gnome-grass/images/gnome-logo.svg";
+#define MODIFIED_SUMMARY_STR   "Modified summary"
 
 typedef struct _ECalMetaBackendTest {
        ECalMetaBackend parent;
+
+       icalcomponent *vcalendar;
+
+       gint sync_tag_index;
+       gboolean can_connect;
+       gboolean is_connected;
+       gint connect_count;
+       gint list_count;
+       gint save_count;
+       gint load_count;
+       gint remove_count;
 } ECalMetaBackendTest;
 
 typedef struct _ECalMetaBackendTestClass {
@@ -37,33 +49,621 @@ typedef struct _ECalMetaBackendTestClass {
 } ECalMetaBackendTestClass;
 
 #define E_TYPE_CAL_META_BACKEND_TEST (e_cal_meta_backend_test_get_type ())
+#define E_CAL_META_BACKEND_TEST(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_CAL_META_BACKEND_TEST, ECalMetaBackendTest))
+#define E_IS_CAL_META_BACKEND_TEST(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_CAL_META_BACKEND_TEST))
 
 GType e_cal_meta_backend_test_get_type (void) G_GNUC_CONST;
 
 G_DEFINE_TYPE (ECalMetaBackendTest, e_cal_meta_backend_test, E_TYPE_CAL_META_BACKEND)
 
 static void
+ecmb_test_add_test_case (ECalMetaBackendTest *test_backend,
+                        const gchar *case_name)
+{
+       gchar *icalstr;
+       icalcomponent *icalcomp;
+
+       g_assert_nonnull (test_backend);
+       g_assert_nonnull (case_name);
+
+       icalstr = tcu_new_icalstring_from_test_case (case_name);
+       g_assert_nonnull (icalstr);
+
+       icalcomp = icalcomponent_new_from_string (icalstr);
+       g_assert_nonnull (icalcomp);
+       g_free (icalstr);
+
+       icalcomponent_add_component (test_backend->vcalendar, icalcomp);
+}
+
+static void
+ecmb_test_remove_component (ECalMetaBackendTest *test_backend,
+                           const gchar *uid,
+                           const gchar *rid)
+{
+       icalcomponent *icalcomp;
+
+       g_assert_nonnull (test_backend);
+       g_assert_nonnull (uid);
+
+       if (rid && !*rid)
+               rid = NULL;
+
+       for (icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;) {
+               const gchar *server_uid;
+
+               server_uid = icalcomponent_get_uid (icalcomp);
+               g_assert_nonnull (server_uid);
+
+               if (g_str_equal (server_uid, uid) && (!rid || !*rid ||
+                   (icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY) &&
+                   g_str_equal (rid, icaltime_as_ical_string (icalcomponent_get_recurrenceid (icalcomp)))))) 
{
+                       icalcomponent_remove_component (test_backend->vcalendar, icalcomp);
+                       icalcomponent_free (icalcomp);
+
+                       icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, 
ICAL_VEVENT_COMPONENT);
+               } else {
+                       icalcomp = icalcomponent_get_next_component (test_backend->vcalendar, 
ICAL_VEVENT_COMPONENT);
+               }
+       }
+}
+
+static GHashTable * /* ECalComponentId * ~> NULL */
+ecmb_test_gather_ids (va_list args)
+{
+       GHashTable *expects;
+       const gchar *uid, *rid;
+
+       expects = g_hash_table_new_full ((GHashFunc) e_cal_component_id_hash, (GEqualFunc) 
e_cal_component_id_equal,
+               (GDestroyNotify) e_cal_component_free_id, NULL);
+
+       uid = va_arg (args, const gchar *);
+       while (uid) {
+               rid = va_arg (args, const gchar *);
+
+               g_hash_table_insert (expects, e_cal_component_id_new (uid, rid), NULL);
+               uid = va_arg (args, const gchar *);
+       }
+
+       return expects;
+}
+
+static void
+ecmb_test_vcalendar_contains (icalcomponent *vcalendar,
+                             gboolean negate,
+                             gboolean exact,
+                             ...) /* <uid, rid> pairs, ended with NULL */
+{
+       va_list args;
+       GHashTable *expects;
+       icalcomponent *icalcomp;
+       guint ntotal;
+
+       g_return_if_fail (vcalendar != NULL);
+       g_return_if_fail (icalcomponent_isa (vcalendar) == ICAL_VCALENDAR_COMPONENT);
+
+       va_start (args, exact);
+       expects = ecmb_test_gather_ids (args);
+       va_end (args);
+
+       ntotal = g_hash_table_size (expects);
+
+       for (icalcomp = icalcomponent_get_first_component (vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;
+            icalcomp = icalcomponent_get_next_component (vcalendar, ICAL_VEVENT_COMPONENT)) {
+               ECalComponentId id;
+
+               id.uid = (gpointer) icalcomponent_get_uid (icalcomp);
+               if (icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY))
+                       id.rid = (gpointer) icaltime_as_ical_string (icalcomponent_get_recurrenceid 
(icalcomp));
+               else
+                       id.rid = NULL;
+
+               if (exact) {
+                       if (negate)
+                               g_assert (!g_hash_table_remove (expects, &id));
+                       else
+                               g_assert (g_hash_table_remove (expects, &id));
+               } else {
+                       g_hash_table_remove (expects, &id);
+               }
+       }
+
+       if (negate)
+               g_assert_cmpint (g_hash_table_size (expects), ==, ntotal);
+       else
+               g_assert_cmpint (g_hash_table_size (expects), ==, 0);
+
+       g_hash_table_destroy (expects);
+}
+
+static void
+ecmb_test_cache_contains (ECalCache *cal_cache,
+                         gboolean negate,
+                         gboolean exact,
+                         ...) /* <uid, rid> pairs, ended with NULL */
+{
+       va_list args;
+       GHashTable *expects;
+       GHashTableIter iter;
+       gpointer key;
+       gint found = 0;
+
+       g_return_if_fail (E_IS_CAL_CACHE (cal_cache));
+
+       va_start (args, exact);
+       expects = ecmb_test_gather_ids (args);
+       va_end (args);
+
+       g_hash_table_iter_init (&iter, expects);
+       while (g_hash_table_iter_next (&iter, &key, NULL)) {
+               ECalComponentId *id = key;
+
+               g_assert_nonnull (id);
+
+               if (e_cal_cache_contains (cal_cache, id->uid, id->rid, E_CACHE_EXCLUDE_DELETED))
+                       found++;
+       }
+
+       if (negate)
+               g_assert_cmpint (0, ==, found);
+       else
+               g_assert_cmpint (g_hash_table_size (expects), ==, found);
+
+       g_hash_table_destroy (expects);
+
+       if (exact && !negate)
+               g_assert_cmpint (e_cache_get_count (E_CACHE (cal_cache), E_CACHE_EXCLUDE_DELETED, NULL, 
NULL), ==, found);
+}
+
+static void
+ecmb_test_cache_and_server_equal (ECalCache *cal_cache,
+                                 icalcomponent *vcalendar,
+                                 ECacheDeletedFlag deleted_flag)
+{
+       icalcomponent *icalcomp;
+
+       g_return_if_fail (E_IS_CAL_CACHE (cal_cache));
+       g_return_if_fail (vcalendar != NULL);
+
+       g_assert_cmpint (e_cache_get_count (E_CACHE (cal_cache), deleted_flag, NULL, NULL), ==,
+               icalcomponent_count_components (vcalendar, ICAL_VEVENT_COMPONENT));
+
+       for (icalcomp = icalcomponent_get_first_component (vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;
+            icalcomp = icalcomponent_get_next_component (vcalendar, ICAL_VEVENT_COMPONENT)) {
+               const gchar *uid, *rid = NULL;
+
+               uid = icalcomponent_get_uid (icalcomp);
+               if (icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY))
+                       rid = icaltime_as_ical_string (icalcomponent_get_recurrenceid (icalcomp));
+
+               g_assert (e_cal_cache_contains (cal_cache, uid, rid, deleted_flag));
+       }
+}
+
+static gchar *
+e_cal_meta_backend_test_get_backend_property (ECalBackend *cal_backend,
+                                             const gchar *prop_name)
+{
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (cal_backend), NULL);
+       g_return_val_if_fail (prop_name != NULL, NULL);
+
+       if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
+               return g_strjoin (",",
+                       e_cal_meta_backend_get_capabilities (E_CAL_META_BACKEND (cal_backend)),
+                       CAL_STATIC_CAPABILITY_ALARM_DESCRIPTION,
+                       NULL);
+       } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS)) {
+               return g_strdup ("user@no.where");
+       }
+
+       /* Chain up to parent's method. */
+       return E_CAL_BACKEND_CLASS (e_cal_meta_backend_test_parent_class)->get_backend_property (cal_backend, 
prop_name);
+}
+
+static gboolean
+e_cal_meta_backend_test_connect_sync (ECalMetaBackend *meta_backend,
+                                     const ENamedParameters *credentials,
+                                     ESourceAuthenticationResult *out_auth_result,
+                                     gchar **out_certificate_pem,
+                                     GTlsCertificateFlags *out_certificate_errors,
+                                     GCancellable *cancellable,
+                                     GError **error)
+{
+       ECalMetaBackendTest *test_backend;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (meta_backend), FALSE);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+
+       if (test_backend->is_connected)
+               return TRUE;
+
+       test_backend->connect_count++;
+
+       if (test_backend->can_connect) {
+               test_backend->is_connected = TRUE;
+               return TRUE;
+       }
+
+       g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE,
+               e_client_error_to_string (E_CLIENT_ERROR_REPOSITORY_OFFLINE));
+
+       return FALSE;
+}
+
+static gboolean
+e_cal_meta_backend_test_disconnect_sync (ECalMetaBackend *meta_backend,
+                                        GCancellable *cancellable,
+                                        GError **error)
+{
+       ECalMetaBackendTest *test_backend;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (meta_backend), FALSE);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       test_backend->is_connected = FALSE;
+
+       return TRUE;
+}
+
+static gboolean
+e_cal_meta_backend_test_get_changes_sync (ECalMetaBackend *meta_backend,
+                                         const gchar *last_sync_tag,
+                                         gchar **out_new_sync_tag,
+                                         gboolean *out_repeat,
+                                         GSList **out_created_objects,
+                                         GSList **out_modified_objects,
+                                         GSList **out_removed_objects,
+                                         GCancellable *cancellable,
+                                         GError **error)
+{
+       ECalMetaBackendTest *test_backend;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (meta_backend), FALSE);
+       g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
+       g_return_val_if_fail (out_repeat != NULL, FALSE);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+
+       if (!test_backend->sync_tag_index) {
+               g_assert_null (last_sync_tag);
+       } else {
+               g_assert_nonnull (last_sync_tag);
+               g_assert_cmpint (atoi (last_sync_tag), ==, test_backend->sync_tag_index);
+
+               test_backend->sync_tag_index++;
+               *out_new_sync_tag = g_strdup_printf ("%d", test_backend->sync_tag_index);
+
+               if (test_backend->sync_tag_index == 2)
+                       *out_repeat = TRUE;
+               else if (test_backend->sync_tag_index == 3)
+                       return TRUE;
+       }
+
+       /* Nothing to do here at the moment, left the work to the parent class,
+          which calls list_existing_sync() internally. */
+       return E_CAL_META_BACKEND_CLASS (e_cal_meta_backend_test_parent_class)->get_changes_sync 
(meta_backend,
+               last_sync_tag, out_new_sync_tag, out_repeat, out_created_objects,
+               out_modified_objects, out_removed_objects, cancellable, error);
+}
+
+static gboolean
+e_cal_meta_backend_test_list_existing_sync (ECalMetaBackend *meta_backend,
+                                           gchar **out_new_sync_tag,
+                                           GSList **out_existing_objects,
+                                           GCancellable *cancellable,
+                                           GError **error)
+{
+       ECalMetaBackendTest *test_backend;
+       ECalCache *cal_cache;
+       icalcomponent *icalcomp;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (meta_backend), FALSE);
+       g_return_val_if_fail (out_new_sync_tag, FALSE);
+       g_return_val_if_fail (out_existing_objects, FALSE);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       test_backend->list_count++;
+
+       g_assert (test_backend->is_connected);
+
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       g_assert_nonnull (cal_cache);
+
+       *out_existing_objects = NULL;
+
+       for (icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;
+            icalcomp = icalcomponent_get_next_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT)) {
+               const gchar *uid;
+               gchar *revision;
+               ECalComponent *comp;
+               ECalMetaBackendInfo *nfo;
+
+               /* Detached instances are stored together with the master object */
+               if (icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY))
+                       continue;
+
+               uid = icalcomponent_get_uid (icalcomp);
+
+               comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (icalcomp));
+               revision = e_cal_cache_dup_component_revision (cal_cache, comp);
+               g_object_unref (comp);
+
+               nfo = e_cal_meta_backend_info_new (uid, NULL, revision);
+               *out_existing_objects = g_slist_prepend (*out_existing_objects, nfo);
+
+               g_free (revision);
+       }
+
+       g_object_unref (cal_cache);
+
+       return TRUE;
+}
+
+static gboolean
+e_cal_meta_backend_test_save_component_sync (ECalMetaBackend *meta_backend,
+                                            gboolean overwrite_existing,
+                                            EConflictResolution conflict_resolution,
+                                            const GSList *instances,
+                                            const gchar *extra,
+                                            gchar **out_new_uid,
+                                            GCancellable *cancellable,
+                                            GError **error)
+{
+       ECalMetaBackendTest *test_backend;
+       icalcomponent *icalcomp;
+       const gchar *uid;
+       GSList *link;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (meta_backend), FALSE);
+       g_return_val_if_fail (instances != NULL, FALSE);
+       g_return_val_if_fail (out_new_uid != NULL, FALSE);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       test_backend->save_count++;
+
+       g_assert (test_backend->is_connected);
+
+       uid = icalcomponent_get_uid (e_cal_component_get_icalcomponent (instances->data));
+       g_assert_nonnull (uid);
+
+       for (icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;) {
+               const gchar *server_uid;
+
+               server_uid = icalcomponent_get_uid (icalcomp);
+               g_assert_nonnull (server_uid);
+
+               if (g_str_equal (server_uid, uid)) {
+                       if (!overwrite_existing) {
+                               g_propagate_error (error, e_data_cal_create_error (ObjectIdAlreadyExists, 
NULL));
+                               return FALSE;
+                       }
+
+                       icalcomponent_remove_component (test_backend->vcalendar, icalcomp);
+                       icalcomponent_free (icalcomp);
+
+                       icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, 
ICAL_VEVENT_COMPONENT);
+               } else {
+                       icalcomp = icalcomponent_get_next_component (test_backend->vcalendar, 
ICAL_VEVENT_COMPONENT);
+               }
+       }
+
+       for (link = (GSList *) instances; link; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+               const gchar *comp_uid;
+
+               icalcomp = e_cal_component_get_icalcomponent (comp);
+               g_assert_nonnull (icalcomp);
+
+               comp_uid = icalcomponent_get_uid (icalcomp);
+               g_assert_cmpstr (uid, ==, comp_uid);
+
+               icalcomponent_add_component (test_backend->vcalendar, icalcomponent_new_clone (icalcomp));
+       }
+
+       *out_new_uid = g_strdup (uid);
+
+       return TRUE;
+}
+
+static gboolean
+e_cal_meta_backend_test_load_component_sync (ECalMetaBackend *meta_backend,
+                                            const gchar *uid,
+                                            icalcomponent **out_instances,
+                                            gchar **out_extra,
+                                            GCancellable *cancellable,
+                                            GError **error)
+{
+       ECalMetaBackendTest *test_backend;
+       icalcomponent *icalcomp;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (meta_backend), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (out_instances != NULL, FALSE);
+       g_return_val_if_fail (out_extra != NULL, FALSE);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       test_backend->load_count++;
+
+       g_assert (test_backend->is_connected);
+
+       *out_instances = NULL;
+
+       for (icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;
+            icalcomp = icalcomponent_get_next_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT)) {
+               const gchar *server_uid;
+
+               server_uid = icalcomponent_get_uid (icalcomp);
+               g_assert_nonnull (server_uid);
+
+               if (g_str_equal (server_uid, uid)) {
+                       if (!*out_instances)
+                               *out_instances = e_cal_util_new_top_level ();
+
+                       icalcomponent_add_component (*out_instances, icalcomponent_new_clone (icalcomp));
+               }
+       }
+
+       if (*out_instances) {
+               *out_extra = g_strconcat ("extra for ", uid, NULL);
+               return TRUE;
+       } else {
+               g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+       }
+
+       return FALSE;
+}
+
+static gboolean
+e_cal_meta_backend_test_remove_component_sync (ECalMetaBackend *meta_backend,
+                                              EConflictResolution conflict_resolution,
+                                              const gchar *uid,
+                                              const gchar *extra,
+                                              GCancellable *cancellable,
+                                              GError **error)
+{
+       ECalMetaBackendTest *test_backend;
+       icalcomponent *icalcomp;
+       gboolean success = FALSE;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND_TEST (meta_backend), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (extra != NULL, FALSE);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       test_backend->remove_count++;
+
+       g_assert (test_backend->is_connected);
+
+       for (icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;) {
+               const gchar *server_uid;
+
+               server_uid = icalcomponent_get_uid (icalcomp);
+               g_assert_nonnull (server_uid);
+
+               if (g_str_equal (server_uid, uid)) {
+                       if (!success) {
+                               gchar *expected_extra;
+
+                               expected_extra = g_strconcat ("extra for ", uid, NULL);
+                               g_assert_cmpstr (expected_extra, ==, extra);
+                               g_free (expected_extra);
+                       }
+
+                       success = TRUE;
+
+                       icalcomponent_remove_component (test_backend->vcalendar, icalcomp);
+                       icalcomponent_free (icalcomp);
+
+                       icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, 
ICAL_VEVENT_COMPONENT);
+               } else {
+                       icalcomp = icalcomponent_get_next_component (test_backend->vcalendar, 
ICAL_VEVENT_COMPONENT);
+               }
+       }
+
+       if (!success)
+               g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+
+       return success;
+}
+
+static void
+e_cal_meta_backend_test_reset_counters (ECalMetaBackendTest *test_backend)
+{
+       g_return_if_fail (E_IS_CAL_META_BACKEND_TEST (test_backend));
+
+       test_backend->connect_count = 0;
+       test_backend->list_count = 0;
+       test_backend->save_count = 0;
+       test_backend->load_count = 0;
+       test_backend->remove_count = 0;
+}
+
+static void
+e_cal_meta_backend_test_finalize (GObject *object)
+{
+       ECalMetaBackendTest *test_backend = E_CAL_META_BACKEND_TEST (object);
+
+       g_assert_nonnull (test_backend->vcalendar);
+
+       icalcomponent_free (test_backend->vcalendar);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_cal_meta_backend_test_parent_class)->finalize (object);
+}
+
+static void
 e_cal_meta_backend_test_class_init (ECalMetaBackendTestClass *klass)
 {
+       ECalMetaBackendClass *cal_meta_backend_class;
+       ECalBackendClass *cal_backend_class;
+       GObjectClass *object_class;
+
+       cal_meta_backend_class = E_CAL_META_BACKEND_CLASS (klass);
+       cal_meta_backend_class->connect_sync = e_cal_meta_backend_test_connect_sync;
+       cal_meta_backend_class->disconnect_sync = e_cal_meta_backend_test_disconnect_sync;
+       cal_meta_backend_class->get_changes_sync = e_cal_meta_backend_test_get_changes_sync;
+       cal_meta_backend_class->list_existing_sync = e_cal_meta_backend_test_list_existing_sync;
+       cal_meta_backend_class->save_component_sync = e_cal_meta_backend_test_save_component_sync;
+       cal_meta_backend_class->load_component_sync = e_cal_meta_backend_test_load_component_sync;
+       cal_meta_backend_class->remove_component_sync = e_cal_meta_backend_test_remove_component_sync;
+
+       cal_backend_class = E_CAL_BACKEND_CLASS (klass);
+       cal_backend_class->get_backend_property = e_cal_meta_backend_test_get_backend_property;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = e_cal_meta_backend_test_finalize;
 }
 
 static void
-e_cal_meta_backend_test_init (ECalMetaBackendTest *test)
+e_cal_meta_backend_test_init (ECalMetaBackendTest *test_backend)
 {
+       test_backend->sync_tag_index = 0;
+       test_backend->is_connected = FALSE;
+       test_backend->can_connect = TRUE;
+       test_backend->vcalendar = e_cal_util_new_top_level ();
+
+       e_cal_meta_backend_test_reset_counters (test_backend);
+
+       e_backend_set_online (E_BACKEND (test_backend), TRUE);
+       e_cal_backend_set_writable (E_CAL_BACKEND (test_backend), TRUE);
+
+       ecmb_test_add_test_case (test_backend, "event-1");
+       ecmb_test_add_test_case (test_backend, "event-2");
+       ecmb_test_add_test_case (test_backend, "event-3");
+       ecmb_test_add_test_case (test_backend, "event-4");
+       ecmb_test_add_test_case (test_backend, "event-5");
+       ecmb_test_add_test_case (test_backend, "event-6");
+       ecmb_test_add_test_case (test_backend, "event-6-a");
+       ecmb_test_add_test_case (test_backend, "event-7");
+       ecmb_test_add_test_case (test_backend, "event-8");
+       ecmb_test_add_test_case (test_backend, "event-9");
 }
 
+static ESourceRegistry *glob_registry = NULL;
+
 static ECalMetaBackend *
 e_cal_meta_backend_test_new (ECalCache *cache)
 {
        ECalMetaBackend *meta_backend;
-       ESourceRegistry *registry;
        ESource *scratch;
+       gboolean success;
        GError *error = NULL;
 
        g_assert (E_IS_CAL_CACHE (cache));
 
-       registry = e_source_registry_new_sync (NULL, NULL);
-       g_assert_nonnull (registry);
+       g_assert_nonnull (glob_registry);
 
        scratch = e_source_new_with_uid ("test-source", NULL, &error);
        g_assert_no_error (error);
@@ -71,20 +671,90 @@ e_cal_meta_backend_test_new (ECalCache *cache)
 
        meta_backend = g_object_new (E_TYPE_CAL_META_BACKEND_TEST,
                "source", scratch,
-               "registry", registry,
+               "registry", glob_registry,
                "kind", ICAL_VEVENT_COMPONENT,
                NULL);
        g_assert_nonnull (meta_backend);
 
-       g_object_unref (registry);
        g_object_unref (scratch);
 
        e_cal_meta_backend_set_cache (meta_backend, cache);
 
+       #define set_extra_data(_uid, _rid) \
+               success = e_cal_cache_set_component_extra (cache, _uid, _rid, "extra for " _uid, NULL, 
&error); \
+               g_assert_no_error (error); \
+               g_assert (success);
+
+       set_extra_data ("event-1", NULL);
+       set_extra_data ("event-2", NULL);
+       set_extra_data ("event-3", NULL);
+       set_extra_data ("event-4", NULL);
+       set_extra_data ("event-5", NULL);
+       set_extra_data ("event-6", NULL);
+       set_extra_data ("event-6", "20170225T134900");
+       set_extra_data ("event-7", NULL);
+       set_extra_data ("event-8", NULL);
+       set_extra_data ("event-9", NULL);
+
+       #undef set_extra_data
+
        return meta_backend;
 }
 
 static void
+e_cal_meta_backend_test_change_online (ECalMetaBackend *meta_backend,
+                                      gboolean is_online)
+{
+       EFlag *flag;
+       gulong handler_id;
+
+       if (!is_online) {
+               e_backend_set_online (E_BACKEND (meta_backend), FALSE);
+               return;
+       }
+
+       if (e_backend_get_online (E_BACKEND (meta_backend)))
+               return;
+
+       flag = e_flag_new ();
+
+       handler_id = g_signal_connect_swapped (meta_backend, "refresh-completed",
+               G_CALLBACK (e_flag_set), flag);
+
+       /* Going online triggers refresh, thus wait for it */
+       e_backend_set_online (E_BACKEND (meta_backend), TRUE);
+
+       e_flag_wait (flag);
+       e_flag_free (flag);
+
+       g_signal_handler_disconnect (meta_backend, handler_id);
+}
+
+static void
+e_cal_meta_backend_test_call_refresh (ECalMetaBackend *meta_backend)
+{
+       EFlag *flag;
+       gulong handler_id;
+       GError *error = NULL;
+
+       if (!e_backend_get_online (E_BACKEND (meta_backend)))
+               return;
+
+       flag = e_flag_new ();
+
+       handler_id = g_signal_connect_swapped (meta_backend, "refresh-completed",
+               G_CALLBACK (e_flag_set), flag);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->refresh_sync (E_CAL_BACKEND_SYNC (meta_backend), NULL, 
NULL, &error);
+       g_assert_no_error (error);
+
+       e_flag_wait (flag);
+       e_flag_free (flag);
+
+       g_signal_handler_disconnect (meta_backend, handler_id);
+}
+
+static void
 assert_tzid_matches_cb (icalparameter *param,
                        gpointer user_data)
 {
@@ -468,11 +1138,1311 @@ test_attachments (TCUFixture *fixture,
        g_free (content);
 }
 
+static void
+test_send_objects (ECalMetaBackend *meta_backend)
+{
+       GSList *users = NULL;
+       const gchar *calobj = "fake-iCalendar-object";
+       gchar *modified_calobj = NULL;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->send_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, calobj, &users, &modified_calobj, &error);
+
+       g_assert_no_error (error);
+       g_assert_null (users);
+       g_assert_cmpstr (calobj, ==, modified_calobj);
+
+       g_free (modified_calobj);
+}
+
+static void
+test_get_attachment_uris (ECalMetaBackend *meta_backend)
+{
+       GSList *uris = NULL;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       /* non-existent event */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_attachment_uris_sync (E_CAL_BACKEND_SYNC 
(meta_backend),
+               NULL, NULL, "unknown-event", NULL, &uris, &error);
+
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (uris);
+
+       g_clear_error (&error);
+
+       /* existent event, but with no attachments */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_attachment_uris_sync (E_CAL_BACKEND_SYNC 
(meta_backend),
+               NULL, NULL, "event-1", NULL, &uris, &error);
+
+       g_assert_no_error (error);
+       g_assert_null (uris);
+
+       /* event with attachments */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_attachment_uris_sync (E_CAL_BACKEND_SYNC 
(meta_backend),
+               NULL, NULL, "event-7", NULL, &uris, &error);
+
+       g_assert_no_error (error);
+       g_assert_nonnull (uris);
+       g_assert_cmpint (g_slist_length (uris), ==, 1);
+       g_assert_cmpstr (uris->data, ==, "file:///usr/share/icons/hicolor/48x48/apps/evolution.png");
+
+       g_slist_free_full (uris, g_free);
+}
+
+static void
+test_discard_alarm (ECalMetaBackend *meta_backend)
+{
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       /* Not implemented */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->discard_alarm_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "unknown-event", NULL, NULL, &error);
+
+       g_assert_error (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED);
+
+       g_clear_error (&error);
+}
+
+static void
+test_timezones (ECalMetaBackend *meta_backend)
+{
+       #define TZID "/meta/backend/test/timezone"
+       #define TZLOC "test/timezone"
+
+       const gchar *in_tzobj =
+               "BEGIN:VTIMEZONE\r\n"
+               "TZID:" TZID "\r\n"
+               "X-LIC-LOCATION:" TZLOC "\r\n"
+               "BEGIN:STANDARD\r\n"
+               "TZNAME:Test-ST\r\n"
+               "DTSTART:19701106T020000\r\n"
+               "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11\r\n"
+               "TZOFFSETFROM:-0400\r\n"
+               "TZOFFSETTO:-0500\r\n"
+               "END:STANDARD\r\n"
+               "BEGIN:DAYLIGHT\r\n"
+               "TZNAME:Test-DT\r\n"
+               "DTSTART:19700313T020000\r\n"
+               "RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3\r\n"
+               "TZOFFSETFROM:-0500\r\n"
+               "TZOFFSETTO:-0400\r\n"
+               "END:DAYLIGHT\r\n"
+               "END:VTIMEZONE\r\n";
+       gchar *tzobj = NULL;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       /* Verify neither TZID, not LOCATION is in the timezone cache */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, TZID, &tzobj, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (tzobj);
+       g_clear_error (&error);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, TZLOC, &tzobj, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (tzobj);
+       g_clear_error (&error);
+
+       /* Add it to the cache */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, in_tzobj, &error);
+       g_assert_no_error (error);
+
+       /* Read it back */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, TZID, &tzobj, &error);
+       g_assert_no_error (error);
+       g_assert_cmpstr (tzobj, ==, in_tzobj);
+       g_free (tzobj);
+       tzobj = NULL;
+
+       /* As a non-built-in timezone it cannot be read with location, only with TZID */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, TZLOC, &tzobj, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (tzobj);
+       g_clear_error (&error);
+
+       /* Try also internal timezone */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "America/New_York", &tzobj, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (tzobj);
+       g_assert (strstr (tzobj, "America/New_York") != NULL);
+       g_free (tzobj);
+       tzobj = NULL;
+
+       #undef TZLOC
+       #undef TZID
+}
+
+static void
+test_get_free_busy (ECalMetaBackend *meta_backend)
+{
+       const gchar *expected_fbobj =
+               "BEGIN:VFREEBUSY\r\n"
+               "ORGANIZER:mailto:user@no.where\r\n";
+               "DTSTART:20170102T080000Z\r\n"
+               "DTEND:20170102T200000Z\r\n"
+               "FREEBUSY;FBTYPE=BUSY;X-SUMMARY=After-party clean up;X-LOCATION=All around:\r\n"
+               " 20170102T100000Z/20170102T180000Z\r\n"
+               "END:VFREEBUSY\r\n";
+       GSList *users, *objects = NULL;
+       time_t start, end;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       users = g_slist_prepend (NULL, (gpointer) "user@no.where");
+       users = g_slist_prepend (users, (gpointer) "unknown@no.where");
+
+       start = icaltime_as_timet (icaltime_from_string ("20170102T080000Z"));
+       end = icaltime_as_timet (icaltime_from_string ("20170102T200000Z"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_free_busy_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, users, start, end, &objects, &error);
+
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (objects), ==, 1);
+       g_assert_cmpstr (objects->data, ==, expected_fbobj);
+
+       g_slist_free_full (objects, g_free);
+       g_slist_free (users);
+}
+
+static void
+test_create_objects (ECalMetaBackend *meta_backend)
+{
+       ECalMetaBackendTest *test_backend;
+       ECalCache *cal_cache;
+       GSList *objects, *uids = NULL, *new_components = NULL, *offline_changes;
+       gchar *calobj, *tmp;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       g_assert_nonnull (cal_cache);
+
+       /* Prepare cache and server content */
+       e_cal_cache_remove_component (cal_cache, "event-7", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+       e_cal_cache_remove_component (cal_cache, "event-8", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+       e_cal_cache_remove_component (cal_cache, "event-9", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+
+       ecmb_test_remove_component (test_backend, "event-7", NULL);
+       ecmb_test_remove_component (test_backend, "event-8", NULL);
+       ecmb_test_remove_component (test_backend, "event-9", NULL);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* Try to add existing event, it should fail */
+       objects = g_slist_prepend (NULL, tcu_new_icalstring_from_test_case ("event-1"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->create_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, &uids, &new_components, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectIdAlreadyExists);
+       g_assert_null (uids);
+       g_assert_null (new_components);
+       g_clear_error (&error);
+       g_slist_free_full (objects, g_free);
+
+       e_cal_meta_backend_test_reset_counters (test_backend);
+
+       /* Try to add new event */
+       objects = g_slist_prepend (NULL, tcu_new_icalstring_from_test_case ("event-7"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->create_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, &uids, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (uids), ==, 1);
+       g_assert_cmpstr (uids->data, ==, "event-7");
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->connect_count, ==, 1);
+       g_assert_cmpint (test_backend->list_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 1);
+       g_assert_cmpint (test_backend->save_count, ==, 1);
+
+       g_slist_free_full (uids, g_free);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (objects, g_free);
+       uids = NULL;
+       new_components = NULL;
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* Going offline */
+       e_cal_meta_backend_test_change_online (meta_backend, FALSE);
+
+       e_cal_meta_backend_test_reset_counters (test_backend);
+
+       /* Try to add existing event, it should fail */
+       objects = g_slist_prepend (NULL, tcu_new_icalstring_from_test_case ("event-7"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->create_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, &uids, &new_components, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectIdAlreadyExists);
+       g_assert_null (uids);
+       g_assert_null (new_components);
+       g_clear_error (&error);
+       g_slist_free_full (objects, g_free);
+       g_assert_cmpint (test_backend->load_count, ==, 0);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+
+       /* Try to add new event */
+       objects = g_slist_prepend (NULL, tcu_new_icalstring_from_test_case ("event-8"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->create_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, &uids, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (uids), ==, 1);
+       g_assert_cmpstr (uids->data, ==, "event-8");
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->connect_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 0);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+
+       g_slist_free_full (uids, g_free);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (objects, g_free);
+       uids = NULL;
+       new_components = NULL;
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "event-8", NULL, NULL);
+       ecmb_test_cache_contains (cal_cache, FALSE, FALSE,
+               "event-8", NULL, NULL);
+
+       /* Going online */
+       e_cal_meta_backend_test_change_online (meta_backend, TRUE);
+
+       g_assert_cmpint (test_backend->connect_count, ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 1);
+       g_assert_cmpint (test_backend->save_count, ==, 1);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       offline_changes = e_cal_cache_get_offline_changes (cal_cache, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (0, ==, g_slist_length (offline_changes));
+
+       /* Add event without UID */
+       calobj = tcu_new_icalstring_from_test_case ("event-9");
+       g_assert_nonnull (calobj);
+       tmp = strstr (calobj, "UID:event-9\r\n");
+       g_assert_nonnull (tmp);
+       strncpy (tmp, "X-TEST:*007", 11);
+
+       objects = g_slist_prepend (NULL, calobj);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->create_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, &uids, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (uids), ==, 1);
+       g_assert_cmpstr (uids->data, !=, "event-9");
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->connect_count, ==, 1);
+       g_assert_cmpint (test_backend->list_count, ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 2);
+       g_assert_cmpint (test_backend->save_count, ==, 2);
+
+       calobj = e_cal_component_get_as_string (new_components->data);
+       g_assert_nonnull (calobj);
+       g_assert_nonnull (strstr (calobj, "X-TEST:*007\r\n"));
+       g_assert_nonnull (strstr (calobj, uids->data));
+
+       g_slist_free_full (uids, g_free);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (objects, g_free);
+       g_free (calobj);
+       uids = NULL;
+       new_components = NULL;
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       g_object_unref (cal_cache);
+}
+
+static gchar *
+ecmb_test_modify_case (const gchar *case_name,
+                      const gchar *ridstr)
+{
+       gchar *calobj;
+       icalcomponent *icalcomp;
+
+       g_assert_nonnull (case_name);
+
+       calobj = tcu_new_icalstring_from_test_case (case_name);
+       g_assert_nonnull (calobj);
+       icalcomp = icalcomponent_new_from_string (calobj);
+       g_assert_nonnull (icalcomp);
+       g_free (calobj);
+
+       icalcomponent_set_summary (icalcomp, MODIFIED_SUMMARY_STR);
+       icalcomponent_set_sequence (icalcomp, icalcomponent_get_sequence (icalcomp) + 1);
+
+       if (ridstr)
+               icalcomponent_set_recurrenceid (icalcomp, icaltime_from_string (ridstr));
+
+       calobj = icalcomponent_as_ical_string_r (icalcomp);
+       icalcomponent_free (icalcomp);
+
+       return calobj;
+}
+
+static void
+test_modify_objects (ECalMetaBackend *meta_backend)
+{
+       ECalMetaBackendTest *test_backend;
+       ECalCache *cal_cache;
+       GSList *objects, *old_components = NULL, *new_components = NULL, *offline_changes;
+       gchar *calobj, *tmp;
+       icalcomponent *icalcomp;
+       gint old_sequence;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       g_assert_nonnull (cal_cache);
+
+       /* Modify non-existing event */
+       calobj = tcu_new_icalstring_from_test_case ("event-1");
+       g_assert_nonnull (calobj);
+       tmp = strstr (calobj, "UID:event-1");
+       g_assert_nonnull (tmp);
+       strncpy (tmp + 4, "unknown", 7);
+
+       objects = g_slist_prepend (NULL, calobj);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->modify_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, E_CAL_OBJ_MOD_ALL, &old_components, &new_components, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (old_components);
+       g_assert_null (new_components);
+       g_clear_error (&error);
+       g_slist_free_full (objects, g_free);
+
+       /* Modify existing event */
+       objects = g_slist_prepend (NULL, ecmb_test_modify_case ("event-1", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->modify_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, E_CAL_OBJ_MOD_ALL, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 1);
+       g_assert_cmpint (test_backend->save_count, ==, 1);
+
+       icalcomp = e_cal_component_get_icalcomponent (old_components->data);
+       old_sequence = icalcomponent_get_sequence (icalcomp);
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), !=, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-1");
+
+       icalcomp = e_cal_component_get_icalcomponent (new_components->data);
+       g_assert_cmpint (old_sequence + 1, ==, icalcomponent_get_sequence (icalcomp));
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), ==, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-1");
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (objects, g_free);
+       old_components = NULL;
+       new_components = NULL;
+
+       /* Going offline */
+       e_cal_meta_backend_test_change_online (meta_backend, FALSE);
+
+       e_cal_meta_backend_test_reset_counters (test_backend);
+
+       /* Modify event-2 */
+       objects = g_slist_prepend (NULL, ecmb_test_modify_case ("event-2", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->modify_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, E_CAL_OBJ_MOD_ALL, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 0);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+
+       icalcomp = e_cal_component_get_icalcomponent (old_components->data);
+       old_sequence = icalcomponent_get_sequence (icalcomp);
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), !=, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-2");
+
+       icalcomp = e_cal_component_get_icalcomponent (new_components->data);
+       g_assert_cmpint (old_sequence + 1, ==, icalcomponent_get_sequence (icalcomp));
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), ==, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-2");
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (objects, g_free);
+       old_components = NULL;
+       new_components = NULL;
+
+       /* Going online */
+       e_cal_meta_backend_test_change_online (meta_backend, TRUE);
+
+       g_assert_cmpint (test_backend->load_count, ==, 1);
+       g_assert_cmpint (test_backend->save_count, ==, 1);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       offline_changes = e_cal_cache_get_offline_changes (cal_cache, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (0, ==, g_slist_length (offline_changes));
+
+       /* Modify non-recurring with THIS */
+       objects = g_slist_prepend (NULL, ecmb_test_modify_case ("event-4", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->modify_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, E_CAL_OBJ_MOD_THIS, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 2);
+       g_assert_cmpint (test_backend->save_count, ==, 2);
+
+       icalcomp = e_cal_component_get_icalcomponent (old_components->data);
+       old_sequence = icalcomponent_get_sequence (icalcomp);
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), !=, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-4");
+
+       icalcomp = e_cal_component_get_icalcomponent (new_components->data);
+       g_assert_cmpint (old_sequence + 1, ==, icalcomponent_get_sequence (icalcomp));
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), ==, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-4");
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (objects, g_free);
+       old_components = NULL;
+       new_components = NULL;
+
+       /* Modify non-detached recurring instance with ONLY_THIS */
+       objects = g_slist_prepend (NULL, ecmb_test_modify_case ("event-6", "20170227T134900"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->modify_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, E_CAL_OBJ_MOD_ONLY_THIS, &old_components, &new_components, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (old_components);
+       g_assert_null (new_components);
+       g_assert_cmpint (test_backend->load_count, ==, 2);
+       g_assert_cmpint (test_backend->save_count, ==, 2);
+       g_clear_error (&error);
+       g_slist_free_full (objects, g_free);
+
+       /* Modify detached recurring instance with ONLY_THIS */
+       objects = g_slist_prepend (NULL, ecmb_test_modify_case ("event-6-a", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->modify_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, objects, E_CAL_OBJ_MOD_ONLY_THIS, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 3);
+       g_assert_cmpint (test_backend->save_count, ==, 3);
+
+       icalcomp = e_cal_component_get_icalcomponent (old_components->data);
+       old_sequence = icalcomponent_get_sequence (icalcomp);
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), !=, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-6");
+
+       icalcomp = e_cal_component_get_icalcomponent (new_components->data);
+       g_assert_cmpint (old_sequence + 1, ==, icalcomponent_get_sequence (icalcomp));
+       g_assert_cmpstr (icalcomponent_get_summary (icalcomp), ==, MODIFIED_SUMMARY_STR);
+       g_assert_cmpstr (icalcomponent_get_uid (icalcomp), ==, "event-6");
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (objects, g_free);
+       old_components = NULL;
+       new_components = NULL;
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       g_object_unref (cal_cache);
+}
+
+static void
+test_remove_objects (ECalMetaBackend *meta_backend)
+{
+       ECalMetaBackendTest *test_backend;
+       ECalCache *cal_cache;
+       GSList *ids, *old_components = NULL, *new_components = NULL, *offline_changes;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       g_assert_nonnull (cal_cache);
+
+       /* Remove non-existing event */
+       ids = g_slist_prepend (NULL, e_cal_component_id_new ("unknown-event", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->remove_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, ids, E_CAL_OBJ_MOD_ALL, &old_components, &new_components, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (old_components);
+       g_assert_null (new_components);
+       g_clear_error (&error);
+       g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
+
+       /* Remove existing event */
+       ids = g_slist_prepend (NULL, e_cal_component_id_new ("event-1", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->remove_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, ids, E_CAL_OBJ_MOD_ALL, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_null (new_components->data);
+       g_assert_cmpint (test_backend->load_count, ==, 0);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->remove_count, ==, 1);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "event-1", NULL,
+               NULL);
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free (new_components);
+       g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
+       old_components = NULL;
+       new_components = NULL;
+
+       /* Remove existing detached instance */
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, FALSE,
+               "event-6", "20170225T134900",
+               NULL);
+
+       ids = g_slist_prepend (NULL, e_cal_component_id_new ("event-6", "20170225T134900"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->remove_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, ids, E_CAL_OBJ_MOD_THIS, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 1);
+       g_assert_cmpint (test_backend->save_count, ==, 1);
+       g_assert_cmpint (test_backend->remove_count, ==, 1);
+
+       /* Master object is there */
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, FALSE,
+               "event-6", NULL,
+               NULL);
+       /* Just-removed detached instance is not there */
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "event-6", "20170225T134900",
+               NULL);
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
+       old_components = NULL;
+       new_components = NULL;
+
+       /* Remove non-existing detached instance with ONLY_THIS - fails */
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "event-6", "20170227T134900",
+               NULL);
+
+       ids = g_slist_prepend (NULL, e_cal_component_id_new ("event-6", "20170227T134900"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->remove_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, ids, E_CAL_OBJ_MOD_ONLY_THIS, &old_components, &new_components, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (old_components);
+       g_assert_null (new_components);
+       g_clear_error (&error);
+       g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
+
+       /* Remove non-existing detached instance with THIS - changes master object */
+       ids = g_slist_prepend (NULL, e_cal_component_id_new ("event-6", "20170227T134900"));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->remove_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, ids, E_CAL_OBJ_MOD_THIS, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_cmpint (test_backend->load_count, ==, 2);
+       g_assert_cmpint (test_backend->save_count, ==, 2);
+       g_assert_cmpint (test_backend->remove_count, ==, 1);
+
+       /* Master object is there */
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, FALSE,
+               "event-6", NULL,
+               NULL);
+       /* Just-removed detached instance is not there */
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "event-6", "20170227T134900",
+               NULL);
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free_full (new_components, g_object_unref);
+       g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
+       old_components = NULL;
+       new_components = NULL;
+
+       /* Going offline */
+       e_cal_meta_backend_test_change_online (meta_backend, FALSE);
+
+       e_cal_meta_backend_test_reset_counters (test_backend);
+
+       /* Remove existing event */
+       ids = g_slist_prepend (NULL, e_cal_component_id_new ("event-3", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->remove_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, ids, E_CAL_OBJ_MOD_ONLY_THIS, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_null (new_components->data);
+       g_assert_cmpint (test_backend->load_count, ==, 0);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, FALSE,
+               "event-3", NULL,
+               NULL);
+       ecmb_test_cache_contains (cal_cache, TRUE, FALSE,
+               "event-3", NULL,
+               NULL);
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free (new_components);
+       g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
+       old_components = NULL;
+       new_components = NULL;
+
+       /* Going online */
+       e_cal_meta_backend_test_change_online (meta_backend, TRUE);
+
+       g_assert_cmpint (test_backend->load_count, ==, 0);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->remove_count, ==, 1);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "event-3", NULL,
+               NULL);
+       ecmb_test_cache_contains (cal_cache, TRUE, FALSE,
+               "event-3", NULL,
+               NULL);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       offline_changes = e_cal_cache_get_offline_changes (cal_cache, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (0, ==, g_slist_length (offline_changes));
+
+       g_object_unref (cal_cache);
+}
+
+static void
+test_receive_objects (ECalMetaBackend *meta_backend)
+{
+       ECalMetaBackendTest *test_backend;
+       ECalCache *cal_cache;
+       gchar *calobj;
+       icalcomponent *icalcomp;
+       GSList *ids, *old_components = NULL, *new_components = NULL;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       g_assert_nonnull (cal_cache);
+
+       /* Organizer side - receives reply from an attendee */
+       calobj = tcu_new_icalstring_from_test_case ("invite-1");
+       g_assert_nonnull (calobj);
+
+       icalcomp = icalcomponent_new_from_string (calobj);
+       g_assert_nonnull (icalcomp);
+       g_assert_nonnull (icalcomponent_get_first_component (icalcomp, ICAL_VEVENT_COMPONENT));
+       g_free (calobj);
+
+       icalcomponent_add_component (test_backend->vcalendar, icalcomponent_new_clone 
(icalcomponent_get_first_component (icalcomp, ICAL_VEVENT_COMPONENT)));
+
+       icalcomponent_free (icalcomp);
+
+       /* To get the 'invite' component into local cache */
+       e_cal_meta_backend_test_call_refresh (meta_backend);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, FALSE,
+               "invite", NULL,
+               NULL);
+       ecmb_test_cache_contains (cal_cache, FALSE, FALSE,
+               "invite", NULL,
+               NULL);
+
+       calobj = NULL;
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "invite", NULL, &calobj, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (calobj);
+       g_assert_nonnull (strstr (calobj, "PARTSTAT=NEEDS-ACTION"));
+       g_assert_null (strstr (calobj, "PARTSTAT=ACCEPTED"));
+       g_free (calobj);
+
+       calobj = tcu_new_icalstring_from_test_case ("invite-2");
+       g_assert_nonnull (calobj);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->receive_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, calobj, &error);
+       g_assert_no_error (error);
+       g_free (calobj);
+
+       g_assert_cmpint (test_backend->load_count, ==, 2);
+       g_assert_cmpint (test_backend->save_count, ==, 1);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       calobj = NULL;
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "invite", NULL, &calobj, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (calobj);
+       g_assert_null (strstr (calobj, "PARTSTAT=NEEDS-ACTION"));
+       g_assert_nonnull (strstr (calobj, "PARTSTAT=ACCEPTED"));
+       g_free (calobj);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* Remove the 'invite' component, to test also user side */
+       ids = g_slist_prepend (NULL, e_cal_component_id_new ("invite", NULL));
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->remove_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, ids, E_CAL_OBJ_MOD_ALL, &old_components, &new_components, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (g_slist_length (old_components), ==, 1);
+       g_assert_cmpint (g_slist_length (new_components), ==, 1);
+       g_assert_null (new_components->data);
+       g_assert_cmpint (test_backend->load_count, ==, 2);
+       g_assert_cmpint (test_backend->save_count, ==, 1);
+       g_assert_cmpint (test_backend->remove_count, ==, 1);
+
+       g_slist_free_full (old_components, g_object_unref);
+       g_slist_free (new_components);
+       g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
+       old_components = NULL;
+       new_components = NULL;
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "invite", NULL,
+               NULL);
+       ecmb_test_cache_contains (cal_cache, TRUE, FALSE,
+               "invite", NULL,
+               NULL);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* User side - receives invitation */
+       calobj = tcu_new_icalstring_from_test_case ("invite-1");
+       g_assert_nonnull (calobj);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->receive_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, calobj, &error);
+       g_assert_no_error (error);
+       g_free (calobj);
+
+       g_assert_cmpint (test_backend->load_count, ==, 3);
+       g_assert_cmpint (test_backend->save_count, ==, 2);
+       g_assert_cmpint (test_backend->remove_count, ==, 1);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, FALSE,
+               "invite", NULL,
+               NULL);
+       ecmb_test_cache_contains (cal_cache, FALSE, FALSE,
+               "invite", NULL,
+               NULL);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       calobj = NULL;
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "invite", NULL, &calobj, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (calobj);
+       g_assert_nonnull (strstr (calobj, "SUMMARY:Invite\r\n"));
+       g_assert_null (strstr (calobj, "SUMMARY:Invite (modified)\r\n"));
+       g_free (calobj);
+
+       /* Receives update from organizer */
+       calobj = tcu_new_icalstring_from_test_case ("invite-3");
+       g_assert_nonnull (calobj);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->receive_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, calobj, &error);
+       g_assert_no_error (error);
+       g_free (calobj);
+
+       g_assert_cmpint (test_backend->load_count, ==, 4);
+       g_assert_cmpint (test_backend->save_count, ==, 3);
+       g_assert_cmpint (test_backend->remove_count, ==, 1);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, FALSE,
+               "invite", NULL,
+               NULL);
+       ecmb_test_cache_contains (cal_cache, FALSE, FALSE,
+               "invite", NULL,
+               NULL);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       calobj = NULL;
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "invite", NULL, &calobj, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (calobj);
+       g_assert_null (strstr (calobj, "SUMMARY:Invite\r\n"));
+       g_assert_nonnull (strstr (calobj, "SUMMARY:Invite (modified)\r\n"));
+       g_free (calobj);
+
+       /* Receives cancellation from organizer */
+       calobj = tcu_new_icalstring_from_test_case ("invite-4");
+       g_assert_nonnull (calobj);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->receive_objects_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, calobj, &error);
+       g_assert_no_error (error);
+       g_free (calobj);
+
+       g_assert_cmpint (test_backend->load_count, ==, 4);
+       g_assert_cmpint (test_backend->save_count, ==, 3);
+       g_assert_cmpint (test_backend->remove_count, ==, 2);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, TRUE, FALSE,
+               "invite", NULL,
+               NULL);
+       ecmb_test_cache_contains (cal_cache, TRUE, FALSE,
+               "invite", NULL,
+               NULL);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       g_object_unref (cal_cache);
+}
+
+static void
+test_get_object (ECalMetaBackend *meta_backend)
+{
+       ECalMetaBackendTest *test_backend;
+       ECalCache *cal_cache;
+       gchar *calobj = NULL;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       g_assert_nonnull (cal_cache);
+
+       e_cal_cache_remove_component (cal_cache, "event-7", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+       e_cal_cache_remove_component (cal_cache, "event-8", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+       e_cal_cache_remove_component (cal_cache, "event-9", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+
+       /* Master object */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "event-6", NULL, &calobj, &error);
+
+       g_assert_no_error (error);
+       g_assert_nonnull (calobj);
+       g_assert (strstr (calobj, "UID:event-6"));
+       g_assert (!strstr (calobj, "RECURRENCE-ID;TZID=America/New_York:20170225T134900"));
+       g_free (calobj);
+       calobj = NULL;
+
+       /* Detached instance */
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "event-6", "20170225T134900", &calobj, &error);
+
+       g_assert_no_error (error);
+       g_assert_nonnull (calobj);
+       g_assert (strstr (calobj, "UID:event-6"));
+       g_assert (strstr (calobj, "RECURRENCE-ID;TZID=America/New_York:20170225T134900"));
+       g_free (calobj);
+       calobj = NULL;
+
+       /* Going offline */
+       e_cal_meta_backend_test_change_online (meta_backend, FALSE);
+
+       g_assert (!e_cal_cache_contains (cal_cache, "event-7", NULL, E_CACHE_EXCLUDE_DELETED));
+
+       e_cal_meta_backend_test_reset_counters (test_backend);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "event-7", NULL, &calobj, &error);
+       g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
+       g_assert_null (calobj);
+       g_clear_error (&error);
+       g_assert_cmpint (test_backend->connect_count, ==, 0);
+       g_assert_cmpint (test_backend->list_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 0);
+
+       /* Going online */
+       e_cal_meta_backend_test_change_online (meta_backend, TRUE);
+
+       g_assert (e_cal_cache_contains (cal_cache, "event-7", NULL, E_CACHE_EXCLUDE_DELETED));
+
+       /* Remove it from the cache, thus it's loaded from the "server" on demand */
+       e_cal_cache_remove_component (cal_cache, "event-7", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+
+       g_assert_cmpint (test_backend->connect_count, ==, 1);
+       e_cal_meta_backend_test_reset_counters (test_backend);
+       g_assert_cmpint (test_backend->connect_count, ==, 0);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "event-7", NULL, &calobj, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (calobj);
+       g_assert_cmpint (test_backend->connect_count, ==, 0);
+       g_assert_cmpint (test_backend->list_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 1);
+       g_assert_nonnull (strstr (calobj, "UID:event-7"));
+       g_free (calobj);
+
+       g_assert (e_cal_cache_contains (cal_cache, "event-7", NULL, E_CACHE_EXCLUDE_DELETED));
+
+       g_object_unref (cal_cache);
+}
+
+static void
+test_get_object_list (ECalMetaBackend *meta_backend)
+{
+       GSList *calobjs = NULL;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_list_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "(uid? \"unknown-event\")", &calobjs, &error);
+       g_assert_no_error (error);
+       g_assert_null (calobjs);
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_list_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "(uid? \"event-3\")", &calobjs, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (calobjs);
+       g_assert_cmpint (g_slist_length (calobjs), ==, 1);
+       g_assert (strstr (calobjs->data, "UID:event-3"));
+       g_slist_free_full (calobjs, g_free);
+       calobjs = NULL;
+
+       E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend)->get_object_list_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, "(uid? \"event-6\")", &calobjs, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (calobjs);
+       g_assert_cmpint (g_slist_length (calobjs), ==, 2);
+       g_assert (strstr (calobjs->data, "UID:event-6"));
+       g_assert (strstr (calobjs->next->data, "UID:event-6"));
+       g_assert_cmpstr (calobjs->data, !=, calobjs->next->data);
+       g_slist_free_full (calobjs, g_free);
+}
+
+static void
+test_refresh (ECalMetaBackend *meta_backend)
+{
+       ECalMetaBackendTest *test_backend;
+       ECalCache *cal_cache;
+       ECache *cache;
+       guint count;
+       icalcomponent *icalcomp;
+       GError *error = NULL;
+
+       g_assert_nonnull (meta_backend);
+
+       test_backend = E_CAL_META_BACKEND_TEST (meta_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       g_assert_nonnull (cal_cache);
+
+       cache = E_CACHE (cal_cache);
+
+       /* Empty local cache */
+       e_cache_remove_all (cache, NULL, &error);
+       g_assert_no_error (error);
+
+       count = e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (count, ==, 0);
+
+       e_cal_meta_backend_test_reset_counters (test_backend);
+
+       ecmb_test_remove_component (test_backend, "event-6", "20170225T134900");
+       ecmb_test_remove_component (test_backend, "event-7", NULL);
+       ecmb_test_remove_component (test_backend, "event-8", NULL);
+       ecmb_test_remove_component (test_backend, "event-9", NULL);
+
+       /* Sync with server content */
+       e_cal_meta_backend_test_call_refresh (meta_backend);
+
+       g_assert_cmpint (test_backend->list_count, ==, 1);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 6);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       count = e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (count, ==, 6);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* Add detached instance, but do not modify the master object, thus it looks like unchanged */
+       ecmb_test_add_test_case (test_backend, "event-6-a");
+
+       /* Sync with server content */
+       e_cal_meta_backend_test_call_refresh (meta_backend);
+
+       g_assert_cmpint (test_backend->list_count, ==, 2);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 6);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       count = e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (count, ==, 6);
+
+       ecmb_test_vcalendar_contains (test_backend->vcalendar, FALSE, TRUE,
+               "event-1", NULL,
+               "event-2", NULL,
+               "event-3", NULL,
+               "event-4", NULL,
+               "event-5", NULL,
+               "event-6", NULL,
+               "event-6", "20170225T134900",
+               NULL);
+
+       ecmb_test_cache_contains (cal_cache, FALSE, TRUE,
+               "event-1", NULL,
+               "event-2", NULL,
+               "event-3", NULL,
+               "event-4", NULL,
+               "event-5", NULL,
+               "event-6", NULL,
+               NULL);
+
+       /* Modify the master object, thus the detached instance will be recognized */
+       for (icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;
+            icalcomp = icalcomponent_get_next_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT)) {
+               if (g_strcmp0 ("event-6", icalcomponent_get_uid (icalcomp)) == 0) {
+                       icalcomponent_set_sequence (icalcomp, icalcomponent_get_sequence (icalcomp) + 1);
+               }
+       }
+
+       /* Sync with server content */
+       e_cal_meta_backend_test_call_refresh (meta_backend);
+
+       g_assert_cmpint (test_backend->list_count, ==, 3);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 7);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       count = e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (count, ==, 7);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* Add some more events */
+       ecmb_test_add_test_case (test_backend, "event-7");
+       ecmb_test_add_test_case (test_backend, "event-9");
+
+       /* Sync with server content */
+       e_cal_meta_backend_test_call_refresh (meta_backend);
+
+       g_assert_cmpint (test_backend->list_count, ==, 4);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 9);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       count = e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (count, ==, 9);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* Remove two events */
+       ecmb_test_remove_component (test_backend, "event-2", NULL);
+       ecmb_test_remove_component (test_backend, "event-4", NULL);
+
+       /* Sync with server content */
+       e_cal_meta_backend_test_call_refresh (meta_backend);
+
+       g_assert_cmpint (test_backend->list_count, ==, 5);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 9);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       count = e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (count, ==, 7);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       /* Mix add/remove/modify */
+       ecmb_test_add_test_case (test_backend, "event-8");
+
+       ecmb_test_remove_component (test_backend, "event-3", NULL);
+       ecmb_test_remove_component (test_backend, "event-6", NULL);
+       ecmb_test_remove_component (test_backend, "event-6", "20170225T134900");
+
+       for (icalcomp = icalcomponent_get_first_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT);
+            icalcomp;
+            icalcomp = icalcomponent_get_next_component (test_backend->vcalendar, ICAL_VEVENT_COMPONENT)) {
+               if (g_strcmp0 ("event-5", icalcomponent_get_uid (icalcomp)) == 0 ||
+                   g_strcmp0 ("event-9", icalcomponent_get_uid (icalcomp)) == 0) {
+                       icalcomponent_set_sequence (icalcomp, icalcomponent_get_sequence (icalcomp) + 1);
+               }
+       }
+
+       /* Sync with server content */
+       e_cal_meta_backend_test_call_refresh (meta_backend);
+
+       g_assert_cmpint (test_backend->list_count, ==, 6);
+       g_assert_cmpint (test_backend->save_count, ==, 0);
+       g_assert_cmpint (test_backend->load_count, ==, 12);
+       g_assert_cmpint (test_backend->remove_count, ==, 0);
+
+       count = e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (count, ==, 5);
+
+       ecmb_test_cache_and_server_equal (cal_cache, test_backend->vcalendar, E_CACHE_INCLUDE_DELETED);
+
+       g_object_unref (cal_cache);
+}
+
+typedef void (* TestWithMainLoopFunc) (ECalMetaBackend *meta_backend);
+
+typedef struct _MainLoopThreadData {
+       TestWithMainLoopFunc func;
+       ECalMetaBackend *meta_backend;
+       GMainLoop *main_loop;
+} MainLoopThreadData;
+
+static gpointer
+test_with_main_loop_thread (gpointer user_data)
+{
+       MainLoopThreadData *mlt = user_data;
+
+       g_assert_nonnull (mlt);
+       g_assert_nonnull (mlt->func);
+       g_assert_nonnull (mlt->meta_backend);
+
+       mlt->func (mlt->meta_backend);
+
+       g_main_loop_quit (mlt->main_loop);
+
+       return NULL;
+}
+
+static gboolean
+quit_test_with_mainloop_cb (gpointer user_data)
+{
+       GMainLoop *main_loop = user_data;
+
+       g_assert_nonnull (main_loop);
+
+       g_main_loop_quit (main_loop);
+
+       g_assert_not_reached ();
+
+       return FALSE;
+}
+
+static gboolean
+test_with_mainloop_run_thread_idle (gpointer user_data)
+{
+       GThread *thread;
+
+       g_assert_nonnull (user_data);
+
+       thread = g_thread_new (NULL, test_with_main_loop_thread, user_data);
+       g_thread_unref (thread);
+
+       return FALSE;
+}
+
+static void
+test_with_main_loop (ECalCache *cal_cache,
+                    TestWithMainLoopFunc func)
+{
+       MainLoopThreadData mlt;
+       ECalMetaBackend *meta_backend;
+       guint timeout_id;
+
+       g_assert_nonnull (cal_cache);
+       g_assert_nonnull (func);
+
+       meta_backend = e_cal_meta_backend_test_new (cal_cache);
+       g_assert_nonnull (meta_backend);
+
+       mlt.func = func;
+       mlt.meta_backend = meta_backend;
+       mlt.main_loop = g_main_loop_new (NULL, FALSE);
+
+       g_idle_add (test_with_mainloop_run_thread_idle, &mlt);
+       timeout_id = g_timeout_add_seconds (10, quit_test_with_mainloop_cb, mlt.main_loop);
+
+       g_main_loop_run (mlt.main_loop);
+
+       g_source_remove (timeout_id);
+       g_main_loop_unref (mlt.main_loop);
+       g_clear_object (&mlt.meta_backend);
+}
+
+#define main_loop_wrapper(_func) \
+static void \
+_func ## _tcu (TCUFixture *fixture, \
+              gconstpointer user_data) \
+{ \
+       test_with_main_loop (fixture->cal_cache, _func); \
+}
+
+main_loop_wrapper (test_send_objects)
+main_loop_wrapper (test_get_attachment_uris)
+main_loop_wrapper (test_discard_alarm)
+main_loop_wrapper (test_timezones)
+main_loop_wrapper (test_get_free_busy)
+main_loop_wrapper (test_create_objects)
+main_loop_wrapper (test_modify_objects)
+main_loop_wrapper (test_remove_objects)
+main_loop_wrapper (test_receive_objects)
+main_loop_wrapper (test_get_object)
+main_loop_wrapper (test_get_object_list)
+main_loop_wrapper (test_refresh)
+
+#undef main_loop_wrapper
+
 gint
 main (gint argc,
       gchar **argv)
 {
        TCUClosure closure_events = { TCU_LOAD_COMPONENT_SET_EVENTS };
+       gint res;
+       GError *error = NULL;
 
 #if !GLIB_CHECK_VERSION (2, 35, 1)
        g_type_init ();
@@ -487,10 +2457,42 @@ main (gint argc,
        icaltzutil_set_exact_vtimezones_support (0);
 #endif
 
+       glob_registry = e_source_registry_new_sync (NULL, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (glob_registry);
+
        g_test_add ("/ECalMetaBackend/MergeInstances", TCUFixture, &closure_events,
                tcu_fixture_setup, test_merge_instances, tcu_fixture_teardown);
        g_test_add ("/ECalMetaBackend/Attachments", TCUFixture, &closure_events,
                tcu_fixture_setup, test_attachments, tcu_fixture_teardown);
-
-       return g_test_run ();
+       g_test_add ("/ECalMetaBackend/SendObjects", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_send_objects_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/GetAttachmentUris", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_get_attachment_uris_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/DiscardAlarm", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_discard_alarm_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/Timezones", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_timezones_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/GetFreeBusy", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_get_free_busy_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/CreateObjects", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_create_objects_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/ModifyObjects", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_modify_objects_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/RemoveObjects", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_remove_objects_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/ReceiveObjects", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_receive_objects_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/GetObject", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_get_object_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/GetObjectList", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_get_object_list_tcu, tcu_fixture_teardown);
+       g_test_add ("/ECalMetaBackend/Refresh", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_refresh_tcu, tcu_fixture_teardown);
+
+       res = g_test_run ();
+
+       g_clear_object (&glob_registry);
+
+       return res;
 }



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