Beispiel #1
0
        private async Task LoadOperationItem(MobileServiceTableOperation operation, OperationBatch batch)
        {
            // only read the item from store if it is not in the operation already
            if (operation.Item == null)
            {
                await TryStoreOperation(async() =>
                {
                    operation.Item = await this.Store.LookupAsync(operation.TableName, operation.ItemId) as JObject;
                }, batch, "Failed to read the item from local store.");

                // Add sync error if item is not found.
                if (operation.Item == null)
                {
                    // Create an item with only id to be able handle error properly.
                    var item      = new JObject(new JProperty(MobileServiceSystemColumns.Id, operation.ItemId));
                    var syncError = new MobileServiceTableOperationError(operation.Id,
                                                                         operation.Version,
                                                                         operation.Kind,
                                                                         null,
                                                                         operation.TableName,
                                                                         item, null, null)
                    {
                        TableKind = this.tableKind,
                        Context   = this.context
                    };
                    await batch.AddSyncErrorAsync(syncError);
                }
            }
        }
 private async Task TryCancelOperation(MobileServiceTableOperationError error)
 {
     if (!await this.opQueue.DeleteAsync(error.Id, error.OperationVersion))
     {
         throw new InvalidOperationException("The operation has been updated and cannot be cancelled.");
     }
     // delete errors for cancelled operation
     await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id);
 }
        public Task CancelAndUpdateItemAsync(MobileServiceTableOperationError error, JObject item)
        {
            string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id);

            return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() =>
            {
                await this.TryCancelOperation(error);
                await this.Store.UpsertAsync(error.TableName, item, fromServer: true);
            }));
        }
        public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error)
        {
            string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id);

            return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() =>
            {
                await this.TryCancelOperation(error);
                await this.Store.DeleteAsync(error.TableName, itemId);
            }));
        }
        private async Task TryUpdateOperation(MobileServiceTableOperationError error, JObject item)
        {
            if (!await this.opQueue.UpdateAsync(error.Id, error.OperationVersion, item))
            {
                throw new InvalidOperationException("The operation has been updated and cannot be updated again");
            }

            // delete errors for updated operation
            await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id);
        }
        public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item)
        {
            string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id);

            return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() =>
            {
                await this.TryUpdateOperation(error, item);
                if (error.OperationKind != MobileServiceTableOperationKind.Delete)
                {
                    await this.Store.UpsertAsync(error.TableName, item, fromServer: true);
                }
            }));
        }
        public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error)
        {
            string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id);

            return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() =>
            {
                await this.TryCancelOperation(error);
                using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings))
                {
                    await trackedStore.DeleteAsync(error.TableName, itemId);
                }
            }));
        }
        public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item)
        {
            string itemId = error.Item.Value <string>(MobileServiceSystemColumns.Id);

            return(this.ExecuteOperationSafeAsync(itemId, error.TableName, async() =>
            {
                await this.TryUpdateOperation(error, item);
                if (error.OperationKind != MobileServiceTableOperationKind.Delete)
                {
                    using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings))
                    {
                        await trackedStore.UpsertAsync(error.TableName, item, fromServer: true);
                    }
                }
            }));
        }
        /// <summary>
        /// Loads all the sync errors in local store that are recorded for this batch.
        /// </summary>
        /// <param name="serializerSettings">the serializer settings to use for reading the errors.</param>
        /// <returns>List of sync errors.</returns>
        public async Task <IList <MobileServiceTableOperationError> > LoadSyncErrorsAsync(MobileServiceJsonSerializerSettings serializerSettings)
        {
            var errors = new List <MobileServiceTableOperationError>();

            JToken result = await this.Store.ReadAsync(new MobileServiceTableQueryDescription(MobileServiceLocalSystemTables.SyncErrors));

            if (result is JArray)
            {
                foreach (JObject error in result)
                {
                    var obj = MobileServiceTableOperationError.Deserialize(error, serializerSettings);
                    obj.Context = this.context;
                    errors.Add(obj);
                }
            }

            return(errors);
        }
        public async Task PushAsync_ReplaysStoredErrors_IfTheyAreInStore()
        {
            var error = new MobileServiceTableOperationError("abc",
                                                            1,
                                                            MobileServiceTableOperationKind.Update,
                                                            HttpStatusCode.PreconditionFailed,
                                                            "test",
                                                            new JObject(),
                                                            "{}",
                                                            new JObject());
            var store = new MobileServiceLocalStoreMock();
            await store.UpsertAsync(MobileServiceLocalSystemTables.SyncErrors, error.Serialize(), fromServer: false);

            var hijack = new TestHttpHandler();
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            var ex = await ThrowsAsync<MobileServicePushFailedException>(service.SyncContext.PushAsync);
        }
 /// <summary>
 ///  Defines all the system tables on the store
 /// </summary>
 /// <param name="store">An instance of <see cref="IMobileServiceLocalStore"/></param>
 public static void DefineAll(MobileServiceLocalStore store)
 {
     MobileServiceTableOperation.DefineTable(store);
     MobileServiceTableOperationError.DefineTable(store);
     MobileServiceSyncSettingsManager.DefineTable(store);
 }
 private async Task TryCancelOperation(MobileServiceTableOperationError error)
 {
     if (!await this.opQueue.DeleteAsync(error.Id, error.OperationVersion))
     {
         throw new InvalidOperationException("The operation has been updated and cannot be cancelled.");
     }
     // delete errors for cancelled operation
     await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id);
 }
 public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error)
 {
     string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id);
     return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () =>
     {
         await this.TryCancelOperation(error);
         using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings))
         {
             await trackedStore.DeleteAsync(error.TableName, itemId);
         }
     });
 }
 public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item)
 {
     string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id);
     return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () =>
     {
         await this.TryUpdateOperation(error, item);
         if (error.OperationKind != MobileServiceTableOperationKind.Delete)
         {
             using (var trackedStore = StoreChangeTrackerFactory.CreateTrackedStore(this.Store, StoreOperationSource.LocalConflictResolution, this.storeTrackingOptions, this.client.EventManager, this.settings))
             {
                 await trackedStore.UpsertAsync(error.TableName, item, fromServer: true);
             }
         }
     });
 }
        private async Task UpdateOperationAsync_ConflictOperation_WithNotificationsEnabled_UsesTheCorrectStoreSource()
        {                        
            var client = new MobileServiceClient("http://www.test.com");
            var store = new MobileServiceLocalStoreMock();
            var context = new MobileServiceSyncContext(client);
            await context.InitializeAsync(store, StoreTrackingOptions.NotifyLocalConflictResolutionOperations);
            var manualResetEvent = new ManualResetEventSlim();

            string operationId = "abc";
            string itemId = "def";
            string tableName = "test";


            store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() { { "id", operationId }, { "version", 1 } } } };
            store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "id", operationId }, { "version", 1 } });
            store.TableMap.Add(tableName, new Dictionary<string, JObject>() { { itemId, new JObject() } });

            // operation exists before cancel
            Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            // item exists before upsert
            Assert.IsNotNull(await store.LookupAsync(tableName, itemId));


            var error = new MobileServiceTableOperationError(operationId,
                                                             1,
                                                             MobileServiceTableOperationKind.Update,
                                                             HttpStatusCode.Conflict,
                                                             tableName,
                                                             item: new JObject() { { "id", itemId } },
                                                             rawResult: "{}",
                                                             result: new JObject());
            var item = new JObject() { { "id", itemId }, { "name", "unknown" } };                       
            
            bool sourceIsLocalConflict = false;
            IDisposable subscription = client.EventManager.Subscribe<StoreOperationCompletedEvent>(o =>
            {
                sourceIsLocalConflict = o.Operation.Source == StoreOperationSource.LocalConflictResolution;
                manualResetEvent.Set();
            });

            await context.UpdateOperationAsync(error, item);

            bool resetEventSignaled = manualResetEvent.Wait(1000);
            subscription.Dispose();

            Assert.IsTrue(resetEventSignaled);
            Assert.IsTrue(sourceIsLocalConflict);
        }
        public async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStore_AndDeletesTheOperationAndError()
        {
            var client = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp);
            var store = new MobileServiceLocalStoreMock();
            var context = new MobileServiceSyncContext(client);
            await context.InitializeAsync(store);

            string operationId = "abc";
            string itemId = "def";
            string tableName = "test";

            store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() } };
            store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject());
            store.TableMap.Add(tableName, new Dictionary<string, JObject>() { { itemId, new JObject() } });

            // operation exists before cancel
            Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            // item exists before upsert
            Assert.IsNotNull(await store.LookupAsync(tableName, itemId));

            var error = new MobileServiceTableOperationError(operationId,
                                                             0,
                                                             MobileServiceTableOperationKind.Update,
                                                             HttpStatusCode.Conflict,
                                                             tableName,
                                                             item: new JObject() { { "id", itemId } },
                                                             rawResult: "{}",
                                                             result: new JObject());

            await context.CancelAndDiscardItemAsync(error);

            // operation is deleted
            Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            // error is deleted
            Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId));

            // item is upserted
            Assert.IsNull(await store.LookupAsync(tableName, itemId));
        }
        private async Task <bool> ExecuteOperationAsync(MobileServiceTableOperation operation, OperationBatch batch)
        {
            if (operation.IsCancelled || this.CancellationToken.IsCancellationRequested)
            {
                return(false);
            }

            operation.Table = await this.context.GetTable(operation.TableName, this.client);

            await this.LoadOperationItem(operation, batch);

            if (this.CancellationToken.IsCancellationRequested)
            {
                return(false);
            }

            await TryUpdateOperationState(operation, MobileServiceTableOperationState.Attempted, batch);

            // strip out system properties before executing the operation
            operation.Item = MobileServiceSyncTable.RemoveSystemPropertiesKeepVersion(operation.Item);

            JObject   result = null;
            Exception error  = null;

            try
            {
                result = await batch.SyncHandler.ExecuteTableOperationAsync(operation);
            }
            catch (Exception ex)
            {
                error = ex;
            }

            if (error != null)
            {
                await TryUpdateOperationState(operation, MobileServiceTableOperationState.Failed, batch);

                if (TryAbortBatch(batch, error))
                {
                    // there is no error to save in sync error and no result to capture
                    // this operation will be executed again next time the push happens
                    return(false);
                }
            }

            // save the result if ExecuteTableOperation did not throw
            if (error == null && result.IsValidItem() && operation.CanWriteResultToStore)
            {
                await TryStoreOperation(() => this.Store.UpsertAsync(operation.TableName, result, fromServer: true), batch, "Failed to update the item in the local store.");
            }
            else if (error != null)
            {
                HttpStatusCode?statusCode = null;
                string         rawResult  = null;
                var            iox        = error as MobileServiceInvalidOperationException;
                if (iox != null && iox.Response != null)
                {
                    statusCode = iox.Response.StatusCode;
                    Tuple <string, JToken> content = await MobileServiceTable.ParseContent(iox.Response, this.client.SerializerSettings);

                    rawResult = content.Item1;
                    result    = content.Item2.ValidItemOrNull();
                }
                var syncError = new MobileServiceTableOperationError(operation.Id,
                                                                     operation.Version,
                                                                     operation.Kind,
                                                                     statusCode,
                                                                     operation.TableName,
                                                                     operation.Item,
                                                                     rawResult,
                                                                     result)
                {
                    TableKind = this.tableKind,
                    Context   = this.context
                };
                await batch.AddSyncErrorAsync(syncError);
            }

            bool success = error == null;

            return(success);
        }
 public Task CancelAndUpdateItemAsync(MobileServiceTableOperationError error, JObject item)
 {
     string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id);
     return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () =>
     {
         await this.TryCancelOperation(error);
         await this.Store.UpsertAsync(error.TableName, item, fromServer: true);
     });
 }
 /// <summary>
 /// Adds a sync error to local store for this batch
 /// </summary>
 /// <param name="error">The sync error that occurred.</param>
 public Task AddSyncErrorAsync(MobileServiceTableOperationError error)
 {
     return(this.Store.UpsertAsync(MobileServiceLocalSystemTables.SyncErrors, error.Serialize(), fromServer: false));
 }
        private async Task<bool> ExecuteOperationAsync(MobileServiceTableOperation operation, OperationBatch batch)
        {
            if (operation.IsCancelled || this.CancellationToken.IsCancellationRequested)
            {
                return false;
            }

            operation.Table = await this.context.GetTable(operation.TableName, this.client);

            await this.LoadOperationItem(operation, batch);

            if (this.CancellationToken.IsCancellationRequested)
            {
                return false;
            }

            await TryUpdateOperationState(operation, MobileServiceTableOperationState.Attempted, batch);

            // strip out system properties before executing the operation
            operation.Item = MobileServiceSyncTable.RemoveSystemPropertiesKeepVersion(operation.Item);

            JObject result = null;
            Exception error = null;
            try
            {
                result = await batch.SyncHandler.ExecuteTableOperationAsync(operation);
            }
            catch (Exception ex)
            {
                error = ex;
            }

            if (error != null)
            {
                await TryUpdateOperationState(operation, MobileServiceTableOperationState.Failed, batch);

                if (TryAbortBatch(batch, error))
                {
                    // there is no error to save in sync error and no result to capture
                    // this operation will be executed again next time the push happens
                    return false;
                }
            }

            // save the result if ExecuteTableOperation did not throw
            if (error == null && result.IsValidItem() && operation.CanWriteResultToStore)
            {
                await TryStoreOperation(() => this.Store.UpsertAsync(operation.TableName, result, fromServer: true), batch, "Failed to update the item in the local store.");
            }
            else if (error != null)
            {
                HttpStatusCode? statusCode = null;
                string rawResult = null;
                var iox = error as MobileServiceInvalidOperationException;
                if (iox != null && iox.Response != null)
                {
                    statusCode = iox.Response.StatusCode;
                    Tuple<string, JToken> content = await MobileServiceTable.ParseContent(iox.Response, this.client.SerializerSettings);
                    rawResult = content.Item1;
                    result = content.Item2.ValidItemOrNull();
                }
                var syncError = new MobileServiceTableOperationError(operation.Id,
                                                                        operation.Version,
                                                                        operation.Kind,
                                                                        statusCode,
                                                                        operation.TableName,
                                                                        operation.Item,
                                                                        rawResult,
                                                                        result)
                                                                        {
                                                                            TableKind = this.tableKind,
                                                                            Context = this.context
                                                                        };
                await batch.AddSyncErrorAsync(syncError);
            }

            bool success = error == null;
            return success;
        }
        public async Task UpdateOperationAsync_UpsertsTheItemInLocalStore_AndDeletesTheError_AndUpdatesTheOperation()
        {
            var client = new MobileServiceClient("http://www.test.com");
            var store = new MobileServiceLocalStoreMock();
            var context = new MobileServiceSyncContext(client);
            await context.InitializeAsync(store);

            string operationId = "abc";
            string itemId = "def";
            string tableName = "test";


            store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() { { "id", operationId }, { "version", 1 } } } };
            store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "id", operationId }, { "version", 1 } });
            store.TableMap.Add(tableName, new Dictionary<string, JObject>() { { itemId, new JObject() } });

            // operation exists before cancel
            Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            // item exists before upsert
            Assert.IsNotNull(await store.LookupAsync(tableName, itemId));

            var error = new MobileServiceTableOperationError(operationId,
                                                             1,
                                                             MobileServiceTableOperationKind.Update,
                                                             HttpStatusCode.Conflict,
                                                             tableName,
                                                             item: new JObject() { { "id", itemId } },
                                                             rawResult: "{}",
                                                             result: new JObject());

            var item = new JObject() { { "id", itemId }, { "name", "unknown" } };
            await context.UpdateOperationAsync(error, item);

            // operation is updated
            Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            // error is deleted
            Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId));

            JObject upserted = await store.LookupAsync(tableName, itemId);
            // item is upserted
            Assert.IsNotNull(upserted);
            Assert.AreEqual(item, upserted);
        }
 /// <summary>
 /// Adds a sync error to local store for this batch
 /// </summary>
 /// <param name="error">The sync error that occurred.</param>
 public Task AddSyncErrorAsync(MobileServiceTableOperationError error)
 {
     return this.Store.UpsertAsync(MobileServiceLocalSystemTables.SyncErrors, error.Serialize(), fromServer: false);
 }
        public async Task UpdateOperationAsync_UpsertTheItemInOperation_AndDeletesTheError()
        {
            var client = new MobileServiceClient("http://www.test.com");
            var store = new MobileServiceLocalStoreMock();
            var context = new MobileServiceSyncContext(client);
            await context.InitializeAsync(store);

            string operationId = "abc";
            string itemId = "def";
            string tableName = "test";

            var item = new JObject() { { "id", itemId }, { "name", "unknown" } };

            store.TableMap[MobileServiceLocalSystemTables.SyncErrors] = new Dictionary<string, JObject>() { { operationId, new JObject() { { "id", operationId }, { "version", 1 } } } };
            store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "id", operationId }, { "version", 1 }, { "item", item.ToString() }, { "kind", (int)MobileServiceTableOperationKind.Delete } });

            // operation exists before cancel
            Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            // item does not exist
            Assert.IsNull(await store.LookupAsync(tableName, itemId));

            var error = new MobileServiceTableOperationError(operationId,
                                                             1,
                                                             MobileServiceTableOperationKind.Delete,
                                                             HttpStatusCode.PreconditionFailed,
                                                             tableName,
                                                             item: new JObject() { { "id", itemId } },
                                                             rawResult: "{}",
                                                             result: new JObject());
            var item2 = new JObject() { { "id", itemId }, { "name", "unknown" }, { "version", 2 } };
            await context.UpdateOperationAsync(error, item2);

            var operation = await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId);
            // operation is updated
            Assert.IsNotNull(operation);
            // error is deleted
            Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.SyncErrors, operationId));

            Assert.AreEqual(operation.GetValue("item").ToString(), item2.ToString(Formatting.None));
        }
 public Task UpdateOperationAsync(MobileServiceTableOperationError error, JObject item)
 {
     string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id);
     return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () =>
     {
         await this.TryUpdateOperation(error, item);
         if (error.OperationKind != MobileServiceTableOperationKind.Delete)
         {
             await this.Store.UpsertAsync(error.TableName, item, fromServer: true);
         }
     });
 }
        private async Task TestOperationModifiedException(bool operationExists, Func<MobileServiceTableOperationError, MobileServiceSyncContext, Task> action, String errorMessage)
        {
            var client = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp);
            var store = new MobileServiceLocalStoreMock();
            var context = new MobileServiceSyncContext(client);
            await context.InitializeAsync(store);

            string operationId = "abc";
            string itemId = "def";
            string tableName = "test";

            if (operationExists)
            {
                store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "version", 3 } });
            }
            else
            {
                // operation exists before cancel
                Assert.IsNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            }

            var error = new MobileServiceTableOperationError(operationId,
                                                             1,
                                                             MobileServiceTableOperationKind.Update,
                                                             HttpStatusCode.Conflict,
                                                             tableName,
                                                             item: new JObject() { { "id", itemId } },
                                                             rawResult: "{}",
                                                             result: new JObject());

            var ex = await ThrowsAsync<InvalidOperationException>(() => action(error, context));

            Assert.AreEqual(ex.Message, errorMessage);
        }
        private async Task TryUpdateOperation(MobileServiceTableOperationError error, JObject item)
        {
            if (!await this.opQueue.UpdateAsync(error.Id, error.OperationVersion, item))
            {
                throw new InvalidOperationException("The operation has been updated and cannot be updated again");
            }

            // delete errors for updated operation
            await this.Store.DeleteAsync(MobileServiceLocalSystemTables.SyncErrors, error.Id);
        }
        private async Task ConflictOperation_WithNotificationsEnabled_UsesTheCorrectStoreOperationSource(Func<MobileServiceSyncContext, MobileServiceTableOperationError, Task> handler)
        {
            var client = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp);
            var store = new MobileServiceLocalStoreMock();
            var context = new MobileServiceSyncContext(client);
            var manualResetEvent = new ManualResetEventSlim();

            await context.InitializeAsync(store, StoreTrackingOptions.NotifyLocalConflictResolutionOperations);

            string operationId = "abc";
            string itemId = string.Empty;
            string tableName = "test";

            store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Add(operationId, new JObject() { { "version", 1 } });


            var error = new MobileServiceTableOperationError(operationId,
                                                             1,
                                                             MobileServiceTableOperationKind.Update,
                                                             HttpStatusCode.Conflict,
                                                             tableName,
                                                             item: new JObject() { { "id", itemId } },
                                                             rawResult: "{}",
                                                             result: new JObject());


            bool sourceIsLocalConflict = false;
            IDisposable subscription = client.EventManager.Subscribe<StoreOperationCompletedEvent>(o =>
            {
                sourceIsLocalConflict = o.Operation.Source == StoreOperationSource.LocalConflictResolution;
                manualResetEvent.Set();
            });

            await handler(context, error);

            bool resetEventSignaled = manualResetEvent.Wait(1000);
            subscription.Dispose();

            Assert.IsTrue(resetEventSignaled);
            Assert.IsTrue(sourceIsLocalConflict);
        }
 public Task CancelAndDiscardItemAsync(MobileServiceTableOperationError error)
 {
     string itemId = error.Item.Value<string>(MobileServiceSystemColumns.Id);
     return this.ExecuteOperationSafeAsync(itemId, error.TableName, async () =>
     {
         await this.TryCancelOperation(error);
         await this.Store.DeleteAsync(error.TableName, itemId);
     });
 }