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_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 PushAsync_DoesNotRunHandler_WhenTableTypeIsNotTable()
        {
            var hijack = new TestHttpHandler();

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

            bool invoked = false;
            var  handler = new MobileServiceSyncHandlerMock();

            handler.TableOperationAction = op =>
            {
                invoked = true;
                throw new InvalidOperationException();
            };

            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(new MobileServiceLocalStoreMock(), handler);

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

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

            await(service.SyncContext as MobileServiceSyncContext).PushAsync(CancellationToken.None, (MobileServiceTableKind)1);

            Assert.IsFalse(invoked);
        }
        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");
        }
        public async Task PushAsync_Succeeds_WithClientWinsPolicy()
        {
            var hijack = new TestHttpHandler();

            hijack.Responses.Add(new HttpResponseMessage(HttpStatusCode.PreconditionFailed)
            {
                Content = new StringContent("{\"id\":\"abc\",\"version\":\"Hey\"}")
            });
            hijack.AddResponseContent(@"{""id"": ""abc""}");

            var handler = new MobileServiceSyncHandlerMock();

            handler.TableOperationAction = async op =>
            {
                for (int i = 0; i < 2; i++)
                {
                    try
                    {
                        return(await op.ExecuteAsync());
                    }
                    catch (MobileServicePreconditionFailedException ex)
                    {
                        op.Item[MobileServiceSystemColumns.Version] = ex.Value[MobileServiceSystemColumns.Version];
                    }
                }
                return(null);
            };
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(new MobileServiceLocalStoreMock(), handler);

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

            await table.UpdateAsync(new JObject()
            {
                { "id", "abc" }, { "version", "Wow" }
            });

            await service.SyncContext.PushAsync();
        }
        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");
        }
        public async Task PullAsync_TriggersPush_FeatureHeaderInOperation()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}"); // for insert
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"World\"}]"); // for pull
            hijack.AddResponseContent("[]"); // last page

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

            // insert an item but don't push
            IMobileServiceSyncTable table = service.GetSyncTable("someTable");
            await table.InsertAsync(new JObject() { { "id", "abc" } });

            // this should trigger a push
            await table.PullAsync(null, null);

            Assert.AreEqual(hijack.Requests[0].Headers.GetValues("X-ZUMO-FEATURES").First(), "TU,OL");
            Assert.AreEqual(hijack.Requests[1].Headers.GetValues("X-ZUMO-FEATURES").First(), "QS,OL");
            Assert.AreEqual(hijack.Requests[2].Headers.GetValues("X-ZUMO-FEATURES").First(), "QS,OL");
        }
        public async Task PullAsync_DoesNotUpdateDeltaToken_AfterEachResult_IfOrderByIsNotSupported()
        {
            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.Responses.Add(new HttpResponseMessage(HttpStatusCode.InternalServerError)); // throw on 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>();
            table.SupportedOptions &= ~MobileServiceRemoteTableOptions.OrderBy;

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

            await ThrowsAsync<MobileServiceInvalidOperationException>(() => table.PullAsync("items", table.CreateQuery()));

            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");

            AssertEx.MatchUris(hijack.Requests, "http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00')&$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')&$skip=2&$top=50&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted");

            Assert.IsFalse(store.TableMap[MobileServiceLocalSystemTables.Config].ContainsKey("deltaToken|stringId_test_table|items"));
        }
        public async Task PullAsync_DoesNotFollowLink_IfRelationIsNotNext()
        {
            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=prev");
            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(MobileAppUriValidator.DummyMobileApp, hijack);
            MobileAppUriValidator mobileAppUriValidator = new MobileAppUriValidator(service);
            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, null);

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

            AssertEx.MatchUris(hijack.Requests, mobileAppUriValidator.GetTableUri("stringId_test_table?$skip=0&$top=50&__includeDeleted=true"),
                                        mobileAppUriValidator.GetTableUri("stringId_test_table?$skip=2&$top=50&__includeDeleted=true"),
                                        mobileAppUriValidator.GetTableUri("stringId_test_table?$skip=4&$top=50&__includeDeleted=true"));
        }
        public async Task PullAsync_Supports_AbsoluteAndRelativeUri()
        {
            var data = new[]
            {
                new 
                {
                    ServiceUri = MobileAppUriValidator.DummyMobileApp, 
                    QueryUri = MobileAppUriValidator.DummyMobileApp + "api/todoitem", 
                    Result = MobileAppUriValidator.DummyMobileApp + "api/todoitem"
                },
                new 
                {
                    ServiceUri = MobileAppUriValidator.DummyMobileAppWithoutTralingSlash, 
                    QueryUri = "/api/todoitem", 
                    Result = MobileAppUriValidator.DummyMobileAppWithoutTralingSlash + "/api/todoitem"
                },
                new 
                {
                    ServiceUri = MobileAppUriValidator.DummyMobileAppUriWithFolder,
                    QueryUri = MobileAppUriValidator.DummyMobileAppUriWithFolder + "api/todoitem", 
                    Result = MobileAppUriValidator.DummyMobileAppUriWithFolder + "api/todoitem"
                },
                new 
                {
                    ServiceUri = MobileAppUriValidator.DummyMobileAppUriWithFolderWithoutTralingSlash, 
                    QueryUri = "/api/todoitem", 
                    Result = MobileAppUriValidator.DummyMobileAppUriWithFolderWithoutTralingSlash + "/api/todoitem"
                }
            };

            foreach (var item 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(item.ServiceUri, hijack);
                MobileAppUriValidator mobileAppUriValidator = new MobileAppUriValidator(service);
                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, item.QueryUri);

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

                AssertEx.MatchUris(hijack.Requests, item.Result + "?$skip=0&$top=50&__includeDeleted=true",
                                                    item.Result + "?$skip=2&$top=50&__includeDeleted=true");

                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 PullAsync_Incremental_PageSize()
        {
            var hijack = new TestHttpHandler();
            hijack.OnSendingRequest = req =>
            {
                return Task.FromResult(req);
            };
            hijack.AddResponseContent(@"[{""id"":""abc"",""String"":""Hey""}]");
            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>();
            PullOptions pullOptions = new PullOptions
            {
                MaxPageSize = 10
            };

            await table.PullAsync("items", table.CreateQuery(), pullOptions: pullOptions);
            AssertEx.MatchUris(hijack.Requests,
                string.Format("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={0}&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted", pullOptions.MaxPageSize),
                string.Format("http://www.test.com/tables/stringId_test_table?$filter=(__updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00')&$orderby=__updatedAt&$skip=1&$top={0}&__includeDeleted=true&__systemproperties=__updatedAt%2C__version%2C__deleted", pullOptions.MaxPageSize));
        }
        public async Task PushAsync_DoesNotRunHandler_WhenTableTypeIsNotTable()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"version\":\"Hey\"}");

            bool invoked = false;
            var handler = new MobileServiceSyncHandlerMock();
            handler.TableOperationAction = op =>
            {
                invoked = true;
                throw new InvalidOperationException();
            };

            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(new MobileServiceLocalStoreMock(), handler);

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

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

            await (service.SyncContext as MobileServiceSyncContext).PushAsync(CancellationToken.None, (MobileServiceTableKind)1);

            Assert.IsFalse(invoked);
        }
        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");
        }
        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 PullAsync_DefaultsTo50_IfGreaterThanMaxPageSize()
        {
            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(MobileAppUriValidator.DummyMobileApp, hijack);
            MobileAppUriValidator mobileAppUriValidator = new MobileAppUriValidator(service);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

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

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

            var pullOptions = new PullOptions
            {
                MaxPageSize = 50,
            };
            await table.PullAsync(null, table.Take(51), pullOptions);

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

            AssertEx.MatchUris(hijack.Requests, mobileAppUriValidator.GetTableUri("stringId_test_table?$skip=0&$top=50&__includeDeleted=true"),
                                        mobileAppUriValidator.GetTableUri("stringId_test_table?$skip=2&$top=49&__includeDeleted=true"));
        }
        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_IfMaxRecordsAreRetrieved()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"How\"}]"); // 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(MobileAppUriValidator.DummyMobileApp, 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.AreEqual(store.TableMap["stringId_test_table"].Count, 1);
        }
        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(MobileAppUriValidator.DummyMobileApp, hijack);
            MobileAppUriValidator mobileAppUriValidator = new MobileAppUriValidator(service);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

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

            await table.PullAsync("items", table.CreateQuery());
            AssertEx.MatchUris(hijack.Requests,
                mobileAppUriValidator.GetTableUri("stringId_test_table?$filter=(updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00')&$orderby=updatedAt&$skip=0&$top=50&__includeDeleted=true"),
                mobileAppUriValidator.GetTableUri("stringId_test_table?$filter=(updatedAt ge datetimeoffset'2001-02-03T00:00:00.0000000%2B00:00')&$orderby=updatedAt&$skip=0&$top=50&__includeDeleted=true"),
                mobileAppUriValidator.GetTableUri("stringId_test_table?$filter=(updatedAt ge datetimeoffset'2001-02-03T00:00:00.0000000%2B00:00')&$orderby=updatedAt&$skip=1&$top=50&__includeDeleted=true"));
        }
        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");
        }
        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 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_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");
        }
        private static async Task TestIncrementalPull(MobileServiceLocalStoreMock store, MobileServiceRemoteTableOptions options, params string[] expectedTableUriComponents)
        {
            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(@"[]");

            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            MobileAppUriValidator mobileAppUriValidator = new MobileAppUriValidator(service);
            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);

            var expectedTableUris =
                expectedTableUriComponents.Select(
                    expectedTableUriComponent => mobileAppUriValidator.GetTableUri(expectedTableUriComponent)).ToArray();

            AssertEx.MatchUris(hijack.Requests, expectedTableUris);
        }
        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 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 PushAsync_ExecutesThePendingOperations_OnAllTables()
        {
            var hijack = new TestHttpHandler();
            var store = new MobileServiceLocalStoreMock();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}");
            hijack.AddResponseContent("{\"id\":\"def\",\"String\":\"Hey\"}");
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);
            await service.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());

            IMobileServiceSyncTable<StringIdType> table = service.GetSyncTable<StringIdType>();
            var item = new StringIdType() { Id = "abc", String = "what?" };
            await table.InsertAsync(item);

            IMobileServiceSyncTable table1 = service.GetSyncTable("someTable");
            await table1.InsertAsync(new JObject() { { "id", "def" } });

            Assert.AreEqual(store.TableMap[table.TableName].Count, 1);
            Assert.AreEqual(store.TableMap[table1.TableName].Count, 1);

            await service.SyncContext.PushAsync();
        }
        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");
        }
        public async Task DeleteAsync_DoesNotUpsertResultOnStore_WhenOperationIsPushed()
        {
            var storeMock = new MobileServiceLocalStoreMock();

            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}"); // for insert
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}"); // for delete
            IMobileServiceClient service = new MobileServiceClient("http://www.test.com", "secret...", hijack);

            await service.SyncContext.InitializeAsync(storeMock, new MobileServiceSyncHandler());

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

            // first add an item
            var item = new StringIdType() { Id = "abc", String = "what?" };
            await table.InsertAsync(item);

            Assert.AreEqual(storeMock.TableMap[table.TableName].Count, 1);

            // for good measure also push it
            await service.SyncContext.PushAsync();

            await table.DeleteAsync(item);

            Assert.AreEqual(storeMock.TableMap[table.TableName].Count, 0);

            // now play it on server
            await service.SyncContext.PushAsync();

            // wait we don't want to upsert the result back because its delete operation
            Assert.AreEqual(storeMock.TableMap[table.TableName].Count, 0);
            // looks good
        }
        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_DoesNotTriggerPush_WhenThereIsNoOperationInTable()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}"); // for insert
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"World\"}]"); // for pull
            hijack.AddResponseContent("[]"); // last page

            var store = new MobileServiceLocalStoreMock();
            store.ReadResponses.Enqueue("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"World\"}]"); // for pull

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

            // insert item in pull 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);

            await table1.PullAsync(null, null);

            Assert.AreEqual(store.TableMap[table1.TableName].Count, 2); // table should contain 2 pulled items
            Assert.AreEqual(hijack.Requests.Count, 3); // 1 for push and 2 for pull
            Assert.AreEqual(store.TableMap[table2.TableName].Count, 1); // this table should not be touched
        }
        public async Task PushAsync_Succeeds_WithClientWinsPolicy()
        {
            var hijack = new TestHttpHandler();
            hijack.Responses.Add(new HttpResponseMessage(HttpStatusCode.PreconditionFailed)
            {
                Content = new StringContent("{\"id\":\"abc\",\"version\":\"Hey\"}")
            });
            hijack.AddResponseContent(@"{""id"": ""abc""}");

            var handler = new MobileServiceSyncHandlerMock();
            handler.TableOperationAction = async op =>
            {
                for (int i = 0; i < 2; i++)
                {
                    try
                    {
                        return await op.ExecuteAsync();
                    }
                    catch (MobileServicePreconditionFailedException ex)
                    {
                        op.Item[MobileServiceSystemColumns.Version] = ex.Value[MobileServiceSystemColumns.Version];
                    }
                }
                return null;
            };
            IMobileServiceClient service = new MobileServiceClient(MobileAppUriValidator.DummyMobileApp, hijack);
            await service.SyncContext.InitializeAsync(new MobileServiceLocalStoreMock(), handler);

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

            await table.UpdateAsync(new JObject() { { "id", "abc" }, { "version", "Wow" } });

            await service.SyncContext.PushAsync();
        }
        public async Task PullAsync_TriggersPush_WhenThereIsOperationInTable()
        {
            var hijack = new TestHttpHandler();
            hijack.AddResponseContent("{\"id\":\"abc\",\"String\":\"Hey\"}"); // for insert
            hijack.AddResponseContent("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"World\"}]"); // for pull
            hijack.AddResponseContent("[]"); // last page

            var store = new MobileServiceLocalStoreMock();
            store.ReadResponses.Enqueue("[{\"id\":\"abc\",\"String\":\"Hey\"},{\"id\":\"def\",\"String\":\"World\"}]"); // for pull

            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 table1 = service.GetSyncTable("someTable");
            await table1.InsertAsync(new JObject() { { "id", "abc" } });
            Assert.AreEqual(store.TableMap[table1.TableName].Count, 1); // item is inserted

            // this should trigger a push
            await table1.PullAsync(null, null);

            Assert.AreEqual(hijack.Requests.Count, 3); // 1 for push and 2 for pull
            Assert.AreEqual(store.TableMap[table1.TableName].Count, 2); // table is populated
        }
        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_UpdatesDeltaToken_AfterEachResult_IfOrderByIsSupported()
        {
            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.Responses.Add(new HttpResponseMessage(HttpStatusCode.InternalServerError)); // throw on second page

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

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

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

            await ThrowsAsync<MobileServiceInvalidOperationException>(() => table.PullAsync("items", table.CreateQuery()));

            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");

            AssertEx.MatchUris(hijack.Requests, mobileAppUriValidator.GetTableUri("stringId_test_table?$filter=(updatedAt ge datetimeoffset'1970-01-01T00:00:00.0000000%2B00:00')&$orderby=updatedAt&$skip=0&$top=50&__includeDeleted=true"),
                                                mobileAppUriValidator.GetTableUri("stringId_test_table?$filter=(updatedAt ge datetimeoffset'2001-02-04T00:00:00.0000000%2B00:00')&$orderby=updatedAt&$skip=0&$top=50&__includeDeleted=true"));

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