public async Task PushAsync_FeatureHeaderPresentWhenRehydrated()
        {
            var hijack = new TestHttpHandler();
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            var store = new MobileServiceLocalStoreMock();
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            JObject item1 = new JObject()
            {
                { "id", "abc" }
            };
            await table.InsertAsync(item1);

            // create a new service to test that operations are loaded from store
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");

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

            await service.SyncContext.PushAsync();

            Assert.AreEqual(hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");
        }
        public async Task PullAsync_Cancels_WhenCancellationTokenIsCancelled()
        {
            var store = new MobileServiceLocalStoreMock();

            var handler = new MobileServiceSyncHandlerMock();
            handler.TableOperationAction = op => Task.Delay(TimeSpan.MaxValue).ContinueWith<JObject>(t => null); // long slow operation
            var hijack = new TestHttpHandler();
            hijack.OnSendingRequest = async req =>
            {
                await Task.Delay(1000);
                return req;
            };
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(store, handler);

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            using (var cts = new CancellationTokenSource())
            {
                Task pullTask = table.PullAsync(null, null, null, cancellationToken: cts.Token);
                cts.Cancel();

                var ex = await ThrowsAsync<Exception>(() => pullTask);

                Assert.IsTrue((ex is OperationCanceledException || ex is TaskCanceledException));
                Assert.AreEqual(pullTask.Status, TaskStatus.Canceled);
            }
        }
        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 async Task CancelAndUpdateItemAsync_UpsertsTheItemInLocalStore_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());

            // operation exists before cancel
            Assert.IsNotNull(await store.LookupAsync(MobileServiceLocalSystemTables.OperationQueue, operationId));
            // item doesn't exist before upsert
            Assert.IsNull(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());

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

            // operation is deleted
            Assert.IsNull(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);
        }
        public async Task PushAsync_ExecutesThePendingOperations_InOrder()
        {
            var hijack = new TestHttpHandler();
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            var store = new MobileServiceLocalStoreMock();
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            JObject item1 = new JObject()
            {
                { "id", "abc" }
            }, item2 = new JObject()
            {
                { "id", "def" }
            };

            await table.InsertAsync(item1);

            await table.InsertAsync(item2);

            Assert.AreEqual(hijack.Requests.Count, 0);

            // create a new service to test that operations are loaded from store
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            hijack.AddResponseContent("{\"id\":\"def\",\"String\":\"What\"}");

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

            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();

            Assert.AreEqual(hijack.Requests.Count, 2);

            Assert.AreEqual(hijack.RequestContents[0], item1.ToString(Formatting.None));
            Assert.AreEqual(hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");
            Assert.AreEqual(hijack.RequestContents[1], item2.ToString(Formatting.None));
            Assert.AreEqual(hijack.Requests[1].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");

            // create yet another service to make sure the old items were purged from queue
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();

            Assert.AreEqual(hijack.Requests.Count, 0);
        }
        public async Task UpdateOperationAsync_UpsertsTheItemInLocalStore_AndDeletesTheError_FromSyncHandler()
        {
            // Arrange
            string itemId = "abc";
            var    hijack = new TestHttpHandler();

            hijack.Responses.Add(new HttpResponseMessage(HttpStatusCode.InternalServerError));

            var handler = new MobileServiceSyncHandlerMock();

            handler.PushCompleteAction = async pushCompletionResult =>
            {
                foreach (var error in pushCompletionResult.Errors)
                {
                    await error.UpdateOperationAsync(JObject.Parse("{\"id\":\"abc\",\"__version\":\"Hey\"}"));
                }
            };
            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", hijack);
            await service.SyncContext.InitializeAsync(store, handler);

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            await table.InsertAsync(new JObject()
            {
                { "id", "abc" }, { "__version", "Wow" }
            });

            // Act
            await(service.SyncContext as MobileServiceSyncContext).PushAsync(CancellationToken.None, MobileServiceTableKind.Table);


            // Assert
            var     syncError = store.TableMap[MobileServiceLocalSystemTables.SyncErrors].Values;
            var     operation = store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Values.FirstOrDefault();
            var     item      = JObject.Parse("{\"id\":\"abc\",\"__version\":\"Hey\"}");
            JObject upserted  = await store.LookupAsync("someTable", itemId);

            // item is upserted
            Assert.IsNotNull(upserted);

            // verify if the record was updated
            Assert.AreEqual(item.ToString(), upserted.ToString());

            // verify if the errors were cleared
            Assert.AreEqual(0, syncError.Count);

            // Verify operation is still present
            Assert.AreEqual(operation.Value <string>("itemId"), itemId);
        }
        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 async Task CancelAndDiscardItemAsync_DeletesTheItemInLocalStore_AndDeletesTheOperationAndError()
        {
            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() }
            };
            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));
        }
        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);
        }
        public async Task PushAsync_ExecutesThePendingOperations_InOrder()
        {
            var hijack = new TestHttpHandler();
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            var store = new MobileServiceLocalStoreMock();
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            JObject item1 = new JObject() { { "id", "abc" } }, item2 = new JObject() { { "id", "def" } };

            await table.InsertAsync(item1);
            await table.InsertAsync(item2);

            Assert.AreEqual(hijack.Requests.Count, 0);

            // create a new service to test that operations are loaded from store
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            hijack.AddResponseContent("{\"id\":\"def\",\"String\":\"What\"}");

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

            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();
            Assert.AreEqual(hijack.Requests.Count, 2);

            Assert.AreEqual(hijack.RequestContents[0], item1.ToString(Formatting.None));
            Assert.AreEqual(hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");
            Assert.AreEqual(hijack.RequestContents[1], item2.ToString(Formatting.None));
            Assert.AreEqual(hijack.Requests[1].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");

            // create yet another service to make sure the old items were purged from queue
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();
            Assert.AreEqual(hijack.Requests.Count, 0);
        }
        public async Task PushAsync_FeatureHeaderPresent()
        {
            var hijack = new TestHttpHandler();

            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");

            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            var store = new MobileServiceLocalStoreMock();
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            JObject item1 = new JObject()
            {
                { "id", "abc" }
            };
            await table.InsertAsync(item1);

            await service.SyncContext.PushAsync();

            Assert.AreEqual(hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");
        }
        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);
        }
        public async Task PullAsync_Cancels_WhenCancellationTokenIsCancelled()
        {
            var store = new MobileServiceLocalStoreMock();

            var handler = new MobileServiceSyncHandlerMock();
            handler.TableOperationAction = op => Task.Delay(TimeSpan.MaxValue).ContinueWith<JObject>(t => null); // long slow operation

            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...");
            await service.SyncContext.InitializeAsync(store, handler);

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            using (var cts = new CancellationTokenSource())
            {
                // now pull
                Task pullTask = table.PullAsync(null, null, null, cancellationToken: cts.Token);
                cts.Cancel();

                var ex = await ThrowsAsync<Exception>(() => pullTask);

                Assert.IsTrue((ex is OperationCanceledException || ex is TaskCanceledException));
                Assert.AreEqual(pullTask.Status, TaskStatus.Canceled);
            }
        }
        public async Task PushAsync_ExecutesThePendingOperations_InOrder_BatchedOne()
        {
            var hijack = new TestHttpHandler();
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            var store = new MobileServiceLocalStoreMock();
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            JObject item1 = new JObject() { { "id", "abc" } }, item2 = new JObject() { { "id", "def" } };

            await table.InsertAsync(item1);
            await table.InsertAsync(item2);

            Assert.AreEqual(hijack.Requests.Count, 0);

            // create a new service to test that operations are loaded from store
            hijack = new TestHttpHandler();

            var content = new MultipartContent("mixed", "6f078995-ef2a-4617-a4c9-5d8746b26d32");
            var response = new HttpResponseMessage(HttpStatusCode.OK) {
                Content = new StringContent("{\"id\":\"abc\",\"String\":\"Hey\"}", Encoding.UTF8, "application/json")
            };
            content.Add(new HttpMessageContent(response));
            hijack.AddResponseContent(content);

            content = new MultipartContent("mixed", "6f078995-ef2a-4617-a4c9-5d8746b26d32");
            response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent("{\"id\":\"def\",\"String\":\"What\"}", Encoding.UTF8, "application/json")
            };
            content.Add(new HttpMessageContent(response));
            hijack.AddResponseContent(content);

            service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
            service.SyncContext.BatchApiEndpoint = "batch";
            service.SyncContext.BatchSize = 1;

            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();
            Assert.AreEqual(hijack.Requests.Count, 2);

            Assert.IsTrue(hijack.RequestContents[0].Contains(item1.ToString(Formatting.None)));
            Assert.IsTrue(hijack.RequestContents[0].Contains("X-ZUMO-FEATURES: TU,OL"));
            Assert.IsTrue(hijack.RequestContents[1].Contains(item2.ToString(Formatting.None)));
            Assert.IsTrue(hijack.RequestContents[1].Contains("X-ZUMO-FEATURES: TU,OL"));

            // create yet another service to make sure the old items were purged from queue
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();
            Assert.AreEqual(hijack.Requests.Count, 0);
        }
        public async Task PullAsync_Supports_AbsoluteAndRelativeUri()
        {
            var data = new string[] 
            {
                "http://www.test.com/api/todoitem",
                "/api/todoitem"
            };

            foreach (string uri in data)
            {
                var hijack = new TestHttpHandler();
                hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"How\"}]"); // first page
                hijack.AddResponseContent("[]");

                var store = new MobileServiceLocalStoreMock();
                IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
                await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

                IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();

                Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));

                await table.PullAsync(null, uri);

                Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 2);

                AssertEx.MatchUris(hijack.Requests, "http://www.test.com/api/todoitem?$skip=0&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted",
                                                    "http://www.test.com/api/todoitem?$skip=2&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted");

                Assert.AreEqual("QS,OL", hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First());
                Assert.AreEqual("QS,OL", hijack.Requests[1].Headers.GetValues("X-ZUMO-FEATURES").First());
            }
        }
        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));
        }
        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);
        }
        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);
        }
        public async Task PushAsync_ExecutesThePendingOperations_InOrder_BatchedOne()
        {
            var hijack = new TestHttpHandler();
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            var store = new MobileServiceLocalStoreMock();
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            JObject item1 = new JObject()
            {
                { "id", "abc" }
            }, item2 = new JObject()
            {
                { "id", "def" }
            };

            await table.InsertAsync(item1);

            await table.InsertAsync(item2);

            Assert.AreEqual(hijack.Requests.Count, 0);

            // create a new service to test that operations are loaded from store
            hijack = new TestHttpHandler();

            var content  = new MultipartContent("mixed", "6f078995-ef2a-4617-a4c9-5d8746b26d32");
            var response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent("{\"id\":\"abc\",\"String\":\"Hey\"}", Encoding.UTF8, "application/json")
            };

            content.Add(new HttpMessageContent(response));
            hijack.AddResponseContent(content);

            content  = new MultipartContent("mixed", "6f078995-ef2a-4617-a4c9-5d8746b26d32");
            response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent("{\"id\":\"def\",\"String\":\"What\"}", Encoding.UTF8, "application/json")
            };
            content.Add(new HttpMessageContent(response));
            hijack.AddResponseContent(content);

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

            service.SyncContext.BatchApiEndpoint = "batch";
            service.SyncContext.BatchSize        = 1;

            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();

            Assert.AreEqual(hijack.Requests.Count, 2);

            Assert.IsTrue(hijack.RequestContents[0].Contains(item1.ToString(Formatting.None)));
            Assert.IsTrue(hijack.RequestContents[0].Contains("X-ZUMO-FEATURES: TU,OL"));
            Assert.IsTrue(hijack.RequestContents[1].Contains(item2.ToString(Formatting.None)));
            Assert.IsTrue(hijack.RequestContents[1].Contains("X-ZUMO-FEATURES: TU,OL"));

            // create yet another service to make sure the old items were purged from queue
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            Assert.AreEqual(hijack.Requests.Count, 0);
            await service.SyncContext.PushAsync();

            Assert.AreEqual(hijack.Requests.Count, 0);
        }
        public async Task PullAsync_Incremental_MovesByUpdatedAt_ThenUsesSkipAndTop_WhenUpdatedAtDoesNotChange()
        {
            var hijack = new TestHttpHandler();
            hijack.OnSendingRequest = req =>
            {
                return Task.FromResult(req);
            };
            hijack.AddResponseContent(@"[{""id"":""abc"",""String"":""Hey"", ""__updatedAt"": ""2001-02-03T00:00:00.0000000+00:00""}]");
            hijack.AddResponseContent(@"[{""id"":""def"",""String"":""World"", ""__updatedAt"": ""2001-02-03T00:00:00.0000000+00:00""}]");
            hijack.AddResponseContent("[]"); // last page

            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();

            await table.PullAsync("items", table.CreateQuery());
            AssertEx.MatchUris(hijack.Requests,
                "http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00')&$orderby=__updatedAt&$skip=0&$top=50&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted",
                "http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'2001-02-03T00:00:00.0000000%2B00:00')&$orderby=__updatedAt&$skip=0&$top=50&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted",
                "http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'2001-02-03T00:00:00.0000000%2B00:00')&$orderby=__updatedAt&$skip=1&$top=50&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted");
        }
        public async Task PullAsync_DoesNotPurge_WhenItemIsMissing()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}"); // for insert
            hijack.AddResponseContent("[{\"id\":\"def\",\"String\":\"World\"}]"); // remote item
            hijack.AddResponseContent("[]"); // last page

            var store = new MobileServiceLocalStoreMock();

            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            await table.InsertAsync(new JObject() { { "id", "abc" } }); // insert an item
            await service.SyncContext.PushAsync(); // push to clear the queue 

            // now pull
            await table.PullAsync(null, null);

            Assert.AreEqual(store.TableMap[table.TableName].Count, 2); // 1 from remote and 1 from local
            Assert.AreEqual(hijack.Requests.Count, 3); // one for push and 2 for pull
        }
        public async Task PullAsync_Throws_WhenPushThrows()
        {
            var hijack = new TestHttpHandler();
            hijack.Responses.Add(new HttpResponseMessage(HttpStatusCode.NotFound)); // for push

            var store = new MobileServiceLocalStoreMock();

            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            // insert an item but don't push
            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            await table.InsertAsync(new JObject() { { "id", "abc" } });
            Assert.AreEqual(store.TableMap[table.TableName].Count, 1); // item is inserted

            // this should trigger a push
            var ex = await ThrowsAsync<MobileServicePushFailedException>(() => table.PullAsync(null, null));

            Assert.AreEqual(ex.PushResult.Errors.Count(), 1);
            Assert.AreEqual(hijack.Requests.Count, 1); // 1 for push 
        }
        public async Task PullAsync_Succeeds()
        {
            var hijack = new TestHttpHandler();
            hijack.OnSendingRequest = req =>
            {
                return Task.FromResult(req);
            };
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"World\"}]"); // for pull
            hijack.AddResponseContent("[]"); // last page

            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();
            var query = table.Skip(5)
                             .Take(3)
                             .Where(t => t.String == "world")
                             .OrderBy(o => o.Id)
                             .WithParameters(new Dictionary<string, string>() { { "param1", "val1" } })
                             .OrderByDescending(o => o.String)
                             .IncludeTotalCount();

            await table.PullAsync(null, query, cancellationToken: CancellationToken.None);
            Assert.AreEqual(hijack.Requests.Count, 2);
            AssertEx.QueryEquals(hijack.Requests[0].RequestUri.Query, "?$filter=(String%20eq%20'world')&$orderby=String%20desc,id&$skip=5&$top=3&param1=val1&__includeDeleted=true&__systemproperties=__version%2C__deleted");
        }
        public async Task PullAsync_DoesNotFollowLink_IfResultIsEmpty()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("[]"); // first page
            hijack.Responses[0].Headers.Add("Link", "http://localhost:31475/tables/Green?$top=1&$select=Text%2CDone%2CId&$skip=2; rel=next");
            hijack.AddResponseContent("[{\"id\":\"ghi\",\"String\":\"Are\"},{\"id\":\"jkl\",\"String\":\"You\"}]"); // second page

            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();

            Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));

            await table.PullAsync(null, table.Take(1));

            Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));
        }
        public async Task PullAsync_DefaultsTo50_IfGreaterThan50()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"How\"}]"); // first page
            hijack.AddResponseContent("[]"); // end of the list

            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();

            Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));

            await table.PullAsync(null, table.Take(51));

            Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 2);

            AssertEx.MatchUris(hijack.Requests, "http://www.test.com/tables/stringId_test_table?$skip=0&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted",
                                        "http://www.test.com/tables/stringId_test_table?$skip=2&$top=49&__includeDeleted=true&__systemproperties=__version%2C__deleted");
        }
        public async Task PullAsync_DoesNotFollowLink_IfLinkHasNonSupportedOptions()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"How\"}]"); // first page
            hijack.Responses[0].Headers.Add("Link", "http://contoso.com:31475/tables/Green?$top=1&$select=Text%2CDone%2CId&$skip=2; rel=next");
            hijack.AddResponseContent("[{\"id\":\"ghi\",\"String\":\"Are\"},{\"id\":\"jkl\",\"String\":\"You\"}]"); // second page
            hijack.AddResponseContent("[]"); // end of the list

            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();
            table.SupportedOptions &= ~MobileServiceRemoteTableOptions.Skip;

            Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));

            await table.PullAsync(null, null);

            Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 2);

            AssertEx.MatchUris(hijack.Requests, "http://www.test.com/tables/stringId_test_table?$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted");
        }
        public async Task PurgeAsync_Throws_WhenThereIsOperationInTable_AndForceIsTrue_AndQueryIsSpecified()
        {
            var hijack = new TestHttpHandler();
            hijack.SetResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            // insert an item but don't push
            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            await table.InsertAsync(new JObject() { { "id", "abc" } });
            Assert.AreEqual(store.TableMap[table.TableName].Count, 1); // item is inserted

            // this should trigger a push
            var ex = await ThrowsAsync<InvalidOperationException>(() => table.PurgeAsync(null, "$filter=a eq b", true, CancellationToken.None));

            Assert.AreEqual(ex.Message, "The table cannot be purged because it has pending operations.");
            Assert.AreEqual(service.SyncContext.PendingOperations, 1L); // operation still in queue
        }
        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 PurgeAsync_DeletesOperations_WhenThereIsOperationInTable_AndForceIsTrue()
        {
            var hijack = new TestHttpHandler();
            hijack.SetResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            // put a dummy delta token
            string deltaKey = "deltaToken|someTable|abc";
            store.TableMap[MobileServiceLocalSystemTables.Config] = new Dictionary<string, JObject>() { { deltaKey, new JObject() } };

            // insert an item but don't push
            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            await table.InsertAsync(new JObject() { { "id", "abc" } });
            Assert.AreEqual(store.TableMap[table.TableName].Count, 1); // item is inserted
            Assert.AreEqual(service.SyncContext.PendingOperations, 1L);

            await table.PurgeAsync("abc", null, force: true, cancellationToken: CancellationToken.None);

            Assert.AreEqual(store.TableMap[table.TableName].Count, 0); // item is deleted
            Assert.AreEqual(service.SyncContext.PendingOperations, 0L); // operation is also removed

            // deleted delta token
            Assert.IsFalse(store.TableMap[MobileServiceLocalSystemTables.Config].ContainsKey(deltaKey));
        }
 private static async Task TestPullAsyncIncrementalWithOptions(MobileServiceRemoteTableOptions options, params string[] uris)
 {
     var store = new MobileServiceLocalStoreMock();
     var settings = new MobileServiceSyncSettingsManager(store);
     await settings.SetDeltaTokenAsync("stringId_test_table", "incquery", new DateTime(2001, 02, 01, 0, 0, 0, DateTimeKind.Utc));
     await TestIncrementalPull(store, options, uris);
 }
        public async Task PushAsync_FeatureHeaderPresentWhenRehydrated()
        {
            var hijack = new TestHttpHandler();
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            var store = new MobileServiceLocalStoreMock();
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            JObject item1 = new JObject() { { "id", "abc" } };
            await table.InsertAsync(item1);

            // create a new service to test that operations are loaded from store
            hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");

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

            Assert.AreEqual(hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");
        }
 public async Task PullAsync_Incremental_WithoutDeltaTokenInDb()
 {
     var store = new MobileServiceLocalStoreMock();
     store.TableMap[MobileServiceLocalSystemTables.Config] = new Dictionary<string, JObject>();
     await TestIncrementalPull(store, MobileServiceRemoteTableOptions.All,
         "http://test.com/tables/stringId_test_table?$filter=((String eq 'world') and (__updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00'))&$orderby=__updatedAt&$skip=0&$top=50&param1=val1&__includeDeleted=true&__systemproperties=__createdAt%2C__updatedAt%2C__deleted",
         "http://test.com/tables/stringId_test_table?$filter=((String eq 'world') and (__updatedAt ge datetimeoffset'2001-02-03T00:00:00.0000000%2B00:00'))&$orderby=__updatedAt&$skip=0&$top=50&param1=val1&__includeDeleted=true&__systemproperties=__createdAt%2C__updatedAt%2C__deleted");
 }
        public async Task UpdateOperationAsync_UpsertsTheItemInLocalStore_AndDeletesTheError_FromSyncHandler()
        {
            // Arrange
            string itemId = "abc";
            var hijack = new TestHttpHandler();
            hijack.Responses.Add(new HttpResponseMessage(HttpStatusCode.InternalServerError));

            var handler = new MobileServiceSyncHandlerMock();
            handler.PushCompleteAction = async pushCompletionResult =>
            {
                foreach (var error in pushCompletionResult.Errors)
                {
                    await error.UpdateOperationAsync(JObject.Parse("{\"id\":\"abc\",\"__version\":\"Hey\"}"));
                }
            };
            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", hijack);
            await service.SyncContext.InitializeAsync(store, handler);

            IMobileServiceSyncTable table = service.GetSyncTable("someTable");

            await table.InsertAsync(new JObject() { { "id", "abc" }, { "__version", "Wow" } });

            // Act
            await (service.SyncContext as MobileServiceSyncContext).PushAsync(CancellationToken.None, MobileServiceTableKind.Table);


            // Assert
            var syncError = store.TableMap[MobileServiceLocalSystemTables.SyncErrors].Values;
            var operation = store.TableMap[MobileServiceLocalSystemTables.OperationQueue].Values.FirstOrDefault();
            var item = JObject.Parse("{\"id\":\"abc\",\"__version\":\"Hey\"}");
            JObject upserted = await store.LookupAsync("someTable", itemId);
            // item is upserted
            Assert.IsNotNull(upserted);

            // verify if the record was updated
            Assert.AreEqual(item.ToString(), upserted.ToString());

            // verify if the errors were cleared
            Assert.AreEqual(0, syncError.Count);

            // Verify operation is still present
            Assert.AreEqual(operation.Value<string>("itemId"),itemId);
        }
        private static async Task TestIncrementalPull(MobileServiceLocalStoreMock store, MobileServiceRemoteTableOptions options, params string[] expectedUris)
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent(@"[{""id"":""abc"",""String"":""Hey"", ""__updatedAt"": ""2001-02-03T00:00:00.0000000+00:00""},
                                        {""id"":""def"",""String"":""World"", ""__updatedAt"": ""2001-02-03T00:03:00.0000000+07:00""}]"); // for pull
            hijack.AddResponseContent(@"[]");


            store.TableMap[MobileServiceLocalSystemTables.Config]["systemProperties|stringId_test_table"] = new JObject
            {
                { MobileServiceSystemColumns.Id, "systemProperties|stringId_test_table" },
                { "value", "1" }
            };

            IMobileServiceClient service = new MobileServiceClient("http://test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();
            table.SupportedOptions = options;
            var query = table.Where(t => t.String == "world")
                             .WithParameters(new Dictionary<string, string>() { { "param1", "val1" } })
                             .IncludeTotalCount();

            await table.PullAsync("incquery", query, cancellationToken: CancellationToken.None);

            AssertEx.MatchUris(hijack.Requests, expectedUris);
        }
        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);
        }
        private async Task TestPullQueryOverrideThrows(IDictionary<string, string> parameters, string errorMessage)
        {
            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...");
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();
            var query = table.CreateQuery()
                             .WithParameters(parameters);

            var ex = await ThrowsAsync<ArgumentException>(() => table.PullAsync(null, query, cancellationToken: CancellationToken.None));
            Assert.AreEqual(errorMessage, ex.Message);
        }
        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));
        }
        public async Task PurgeAsync_DoesNotTriggerPush_WhenThereIsNoOperationInTable()
        {
            var hijack = new TestHttpHandler();
            hijack.SetResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            // insert item in purge table
            IMobileServiceSyncTable table1 = service.GetSyncTable("someTable");
            await table1.InsertAsync(new JObject() { { "id", "abc" } });

            // but push to clear the queue
            await service.SyncContext.PushAsync();
            Assert.AreEqual(store.TableMap[table1.TableName].Count, 1); // item is inserted
            Assert.AreEqual(hijack.Requests.Count, 1); // first push

            // then insert item in other table
            IMobileServiceSyncTable<StringIdType> table2 = service.GetSyncTable<StringIdType>();
            var item = new StringIdType() { Id = "an id", String = "what?" };
            await table2.InsertAsync(item);

            // try purge on first table now
            await table1.PurgeAsync();

            Assert.AreEqual(store.DeleteQueries[0].TableName, MobileServiceLocalSystemTables.SyncErrors); // push deletes all sync erros
            Assert.AreEqual(store.DeleteQueries[1].TableName, table1.TableName); // purged table
            Assert.AreEqual(hijack.Requests.Count, 1); // still 1 means no other push happened
            Assert.AreEqual(store.TableMap[table2.TableName].Count, 1); // this table should not be touched
        }
        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 PurgeAsync_ResetsDeltaToken_WhenQueryIdIsSpecified()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent(@"[{""id"":""abc"",""String"":""Hey"", ""__updatedAt"": ""2001-02-03T00:00:00.0000000+00:00""},
                                         {""id"":""def"",""String"":""How"", ""__updatedAt"": ""2001-02-04T00:00:00.0000000+00:00""}]"); // first page
            hijack.AddResponseContent("[]"); // last page of first pull
            hijack.AddResponseContent("[]"); // second pull

            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();

            // ensure there is no delta token present already
            Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));

            // now pull down data
            await table.PullAsync("items", table.CreateQuery());

            // ensure items were pulled down
            Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 2);
            Assert.AreEqual(store.TableMap["stringId_test_table"]["abc"].Value<string>("String"), "Hey");
            Assert.AreEqual(store.TableMap["stringId_test_table"]["def"].Value<string>("String"), "How");

            // ensure delta token was updated
            Assert.Equals(store.TableMap[MobileServiceLocalSystemTables.Config]["deltaToken|stringId_test_table|items"]["value"], "2001-02-04T00:00:00.0000000+00:00");

            // now purge and forget the delta token
            await table.PurgeAsync("items", null, false, CancellationToken.None);

            // make sure data is purged
            Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 0);
            // make sure delta token is removed
            Assert.IsFalse(store.TableMap[MobileServiceLocalSystemTables.Config].ContainsKey("deltaToken|stringId_test_table|items"));

            // pull again
            await table.PullAsync("items", table.CreateQuery());

            // verify request urls
            AssertEx.MatchUris(hijack.Requests, "http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00')&$orderby=__updatedAt&$skip=0&$top=50&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted",
                                                "http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'2001-02-04T00:00:00.0000000%2B00:00')&$orderby=__updatedAt&$skip=0&$top=50&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted",
                                                "http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00')&$orderby=__updatedAt&$skip=0&$top=50&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted");
        }
        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 async Task PullAsync_UsesSkipAndTakeThenFollowsLinkThenUsesSkipAndTake()
        {
            var hijack = new TestHttpHandler();
            // first page
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"How\"}]");
            // second page with a link
            hijack.AddResponseContent("[{\"id\":\"ghi\",\"String\":\"Are\"},{\"id\":\"jkl\",\"String\":\"You\"}]");
            hijack.Responses[1].Headers.Add("Link", "http://localhost:31475/tables/Green?$top=1&$select=Text%2CDone%2CId&$skip=2; rel=next");
            // forth page without link
            hijack.AddResponseContent("[{\"id\":\"mno\",\"String\":\"Mr\"},{\"id\":\"pqr\",\"String\":\"X\"}]");
            // last page
            hijack.AddResponseContent("[]");

            var store = new MobileServiceLocalStoreMock();
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<ToDoWithStringId> table = service.GetSyncTable<ToDoWithStringId>();

            Assert.IsFalse(store.TableMap.ContainsKey("stringId_test_table"));

            await table.PullAsync(null, table.Take(51).Skip(3));

            Assert.AreEqual(store.TableMap["stringId_test_table"].Count, 6);
            Assert.AreEqual(store.TableMap["stringId_test_table"]["abc"].Value<string>("String"), "Hey");
            Assert.AreEqual(store.TableMap["stringId_test_table"]["def"].Value<string>("String"), "How");
            Assert.AreEqual(store.TableMap["stringId_test_table"]["ghi"].Value<string>("String"), "Are");
            Assert.AreEqual(store.TableMap["stringId_test_table"]["jkl"].Value<string>("String"), "You");
            Assert.AreEqual(store.TableMap["stringId_test_table"]["mno"].Value<string>("String"), "Mr");
            Assert.AreEqual(store.TableMap["stringId_test_table"]["pqr"].Value<string>("String"), "X");

            AssertEx.MatchUris(hijack.Requests, "http://www.test.com/tables/stringId_test_table?$skip=3&$top=50&__includeDeleted=true&__systemproperties=__version%2C__deleted",
                                        "http://www.test.com/tables/stringId_test_table?$skip=5&$top=49&__includeDeleted=true&__systemproperties=__version%2C__deleted",
                                        "http://localhost:31475/tables/Green?$top=1&$select=Text%2CDone%2CId&$skip=2",
                                        "http://www.test.com/tables/stringId_test_table?$skip=9&$top=45&__includeDeleted=true&__systemproperties=__version%2C__deleted");
        }