com.microsoft.windowsazure.mobileservices.sdk.testapp.test.MobileServiceFeaturesTests.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.windowsazure.mobileservices.sdk.testapp.test.MobileServiceFeaturesTests.java

Source

/*
Copyright (c) Microsoft Open Technologies, Inc.
All Rights Reserved
Apache 2.0 License
     
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     
 http://www.apache.org/licenses/LICENSE-2.0
     
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
     
See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
 */
package com.microsoft.windowsazure.mobileservices.sdk.testapp.test;

import android.net.Uri;
import android.test.InstrumentationTestCase;
import android.util.Pair;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.JsonObject;
import com.microsoft.windowsazure.mobileservices.MobileServiceClient;
import com.microsoft.windowsazure.mobileservices.MobileServiceException;
import com.microsoft.windowsazure.mobileservices.MobileServiceFeatures;
import com.microsoft.windowsazure.mobileservices.http.HttpConstants;
import com.microsoft.windowsazure.mobileservices.http.NextServiceFilterCallback;
import com.microsoft.windowsazure.mobileservices.http.ServiceFilter;
import com.microsoft.windowsazure.mobileservices.http.ServiceFilterRequest;
import com.microsoft.windowsazure.mobileservices.http.ServiceFilterResponse;
import com.microsoft.windowsazure.mobileservices.sdk.testapp.framework.filters.ServiceFilterResponseMock;
import com.microsoft.windowsazure.mobileservices.sdk.testapp.framework.mocks.MobileServiceLocalStoreMock;
import com.microsoft.windowsazure.mobileservices.sdk.testapp.test.types.Address;
import com.microsoft.windowsazure.mobileservices.sdk.testapp.test.types.PersonTestObjectWithStringId;
import com.microsoft.windowsazure.mobileservices.sdk.testapp.test.types.SystemPropertyTestClasses.VersionType;
import com.microsoft.windowsazure.mobileservices.table.MobileServiceJsonTable;
import com.microsoft.windowsazure.mobileservices.table.MobileServiceTable;
import com.microsoft.windowsazure.mobileservices.table.query.ExecutableJsonQuery;
import com.microsoft.windowsazure.mobileservices.table.query.ExecutableQuery;
import com.microsoft.windowsazure.mobileservices.table.sync.MobileServiceJsonSyncTable;
import com.microsoft.windowsazure.mobileservices.table.sync.MobileServiceSyncTable;
import com.microsoft.windowsazure.mobileservices.table.sync.localstore.ColumnDataType;
import com.microsoft.windowsazure.mobileservices.table.sync.synchandler.SimpleSyncHandler;
import com.squareup.okhttp.Headers;

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

public class MobileServiceFeaturesTests extends InstrumentationTestCase {
    String appUrl;

    protected void setUp() throws Exception {
        appUrl = "http://myapp.com/";
        super.setUp();
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void testFeaturesToStringConversion() {
        Hashtable<EnumSet<MobileServiceFeatures>, String> cases;
        cases = new Hashtable<EnumSet<MobileServiceFeatures>, String>();
        for (MobileServiceFeatures feature : MobileServiceFeatures.class.getEnumConstants()) {
            cases.put(EnumSet.of(feature), feature.getValue());
        }
        cases.put(EnumSet.of(MobileServiceFeatures.TypedTable, MobileServiceFeatures.AdditionalQueryParameters),
                "QS,TT");
        cases.put(EnumSet.of(MobileServiceFeatures.UntypedTable, MobileServiceFeatures.AdditionalQueryParameters),
                "QS,TU");
        cases.put(EnumSet.of(MobileServiceFeatures.TypedTable, MobileServiceFeatures.Offline), "OL,TT");
        cases.put(EnumSet.of(MobileServiceFeatures.UntypedTable, MobileServiceFeatures.Offline), "OL,TU");
        cases.put(EnumSet.of(MobileServiceFeatures.TypedApiCall, MobileServiceFeatures.AdditionalQueryParameters),
                "AT,QS");
        cases.put(EnumSet.of(MobileServiceFeatures.JsonApiCall, MobileServiceFeatures.AdditionalQueryParameters),
                "AJ,QS");
        cases.put(EnumSet.of(MobileServiceFeatures.OpportunisticConcurrency, MobileServiceFeatures.Offline,
                MobileServiceFeatures.UntypedTable), "OC,OL,TU");

        for (EnumSet<MobileServiceFeatures> features : cases.keySet()) {
            String expected = cases.get(features);
            String actual = MobileServiceFeatures.featuresToString(features);
            assertEquals(expected, actual);
        }
    }

    // Typed tables
    public void testTypedTableInsertFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                typedTable.insert(pto).get();
            }
        }, false, "TT");
    }

    public void testTypedTableInsertWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                typedTable.insert(pto, queryParams).get();
            }
        }, false, "QS,TT");
    }

    public void testTypedTableInsertWithEmptyParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                typedTable.insert(pto, queryParams).get();
            }
        }, false, "TT");
    }

    public void testTypedTableUpdateFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                pto.setId("the-id");
                typedTable.update(pto).get();
            }
        }, false, "TT");
    }

    public void testTypedTableUpdateWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                pto.setId("the-id");
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                typedTable.update(pto, queryParams).get();
            }
        }, false, "QS,TT");
    }

    public void testTypedTableDeleteNoParametersNoFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                pto.setId("the-id");
                typedTable.delete(pto).get();
            }
        }, false, "TT");
    }

    public void testTypedTableDeleteWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                pto.setId("the-id");
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                typedTable.delete(pto, queryParams).get();
            }
        }, false, "QS,TT");
    }

    public void testTypedTableLookupFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                typedTable.lookUp("1").get();
            }
        }, false, "TT");
    }

    public void testTypedTableLookupWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                typedTable.lookUp("1", queryParams).get();
            }
        }, false, "QS,TT");
    }

    public void testTypedTableReadFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                typedTable.execute().get();
            }
        }, true, "TT");
    }

    public void testTypedQueryFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                typedTable.parameter("a", "b").execute().get();
            }
        }, true, "QS,TT");
    }

    // JSON tables
    private JsonObject createJsonObject() {
        JsonObject result = new JsonObject();
        result.addProperty("id", "the-id");
        result.addProperty("firstName", "John");
        result.addProperty("lastName", "Doe");
        result.addProperty("age", 33);
        return result;
    }

    public void testJsonTableInsertFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                JsonObject jo = createJsonObject();
                jsonTable.insert(jo).get();
            }
        }, false, "TU");
    }

    public void testJsonTableInsertWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                JsonObject jo = createJsonObject();
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                jsonTable.insert(jo, queryParams).get();
            }
        }, false, "QS,TU");
    }

    public void testJsonTableInsertWithEmptyParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                JsonObject jo = createJsonObject();
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                jsonTable.insert(jo, queryParams).get();
            }
        }, false, "TU");
    }

    public void testJsonTableUpdateFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                JsonObject jo = createJsonObject();
                jsonTable.update(jo).get();
            }
        }, false, "TU");
    }

    public void testJsonTableUpdateWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                JsonObject jo = createJsonObject();
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                jsonTable.update(jo, queryParams).get();
            }
        }, false, "QS,TU");
    }

    public void testJsonTableDeleteFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                JsonObject jo = createJsonObject();
                jsonTable.delete(jo).get();
            }
        }, false, "TU");
    }

    public void testJsonTableDeleteWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                JsonObject jo = createJsonObject();
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                jsonTable.delete(jo, queryParams).get();
            }
        }, false, "QS,TU");
    }

    public void testJsonTableLookupFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                jsonTable.lookUp("1").get();
            }
        }, false, "TU");
    }

    public void testJsonTableLookupWithParametersFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                jsonTable.lookUp("1", queryParams).get();
            }
        }, false, "QS,TU");
    }

    public void testJsonTableReadFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                jsonTable.execute().get();
            }
        }, true, "TU");
    }

    public void testJsonQueryFeatureHeader() {
        testTableFeatureHeader(new TableTestOperation() {

            @Override
            public void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonTable jsonTable) throws Exception {
                jsonTable.parameter("a", "b").execute().get();
            }
        }, true, "QS,TU");
    }

    private void testTableFeatureHeader(TableTestOperation operation, final boolean responseIsArray,
            final String expectedFeaturesHeader) {
        MobileServiceClient client = null;
        try {
            client = new MobileServiceClient(appUrl, getInstrumentation().getTargetContext());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        // Add a new filter to the client
        client = client.withFilter(new ServiceFilter() {

            @Override
            public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request,
                    NextServiceFilterCallback nextServiceFilterCallback) {

                final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
                String featuresHeaderName = "X-ZUMO-FEATURES";

                Headers headers = request.getHeaders();
                String features = null;
                for (int i = 0; i < headers.size(); i++) {
                    if (headers.name(i) == featuresHeaderName) {
                        features = headers.value(i);
                    }
                }

                if (features == null) {
                    resultFuture.setException(new Exception("No " + featuresHeaderName + " header on API call"));
                } else if (!features.equals(expectedFeaturesHeader)) {
                    resultFuture.setException(new Exception("Incorrect features header; expected "
                            + expectedFeaturesHeader + ", actual " + features));
                } else {
                    ServiceFilterResponseMock response = new ServiceFilterResponseMock();
                    String content = "{\"id\":\"the-id\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":33}";
                    if (responseIsArray) {
                        content = "[" + content + "]";
                    }
                    response.setContent(content);
                    resultFuture.set(response);
                }

                return resultFuture;
            }
        });

        try {
            MobileServiceTable<PersonTestObjectWithStringId> typedTable = client
                    .getTable(PersonTestObjectWithStringId.class);
            MobileServiceJsonTable jsonTable = client.getTable("Person");
            operation.executeOperation(typedTable, jsonTable);
        } catch (Exception exception) {
            Throwable ex = exception;
            while (ex instanceof ExecutionException || ex instanceof MobileServiceException) {
                ex = ex.getCause();
            }
            ex.printStackTrace();
            fail(ex.getMessage());
        }
    }

    public void testJsonApiFeatureHeader() {
        testInvokeApiFeatureHeader(new ClientTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                client.invokeApi("foo").get();
            }

        }, "AJ");
    }

    public void testJsonApiWithQueryParametersFeatureHeader() {
        testInvokeApiFeatureHeader(new ClientTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                client.invokeApi("apiName", HttpConstants.DeleteMethod, queryParams).get();
            }

        }, "AJ,QS");
    }

    public void testTypedApiFeatureHeader() {
        testInvokeApiFeatureHeader(new ClientTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                client.invokeApi("apiName", Address.class).get();
            }

        }, "AT");
    }

    public void testTypedApiWithQueryParametersFeatureHeader() {
        testInvokeApiFeatureHeader(new ClientTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                client.invokeApi("apiName", HttpConstants.GetMethod, queryParams, Address.class).get();
            }

        }, "AT,QS");
    }

    public void testGenericApiFeatureHeader() {
        testInvokeApiFeatureHeader(new ClientTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                List<Pair<String, String>> requestHeaders = new ArrayList<Pair<String, String>>();
                requestHeaders.add(new Pair<String, String>(HttpConstants.ContentType, "text/plain"));
                byte[] content = "hello world".getBytes();
                client.invokeApi("apiName", content, HttpConstants.PostMethod, requestHeaders, queryParams).get();
            }

        }, "AG");
    }

    public void testGenericApiDoesNotOverrideExistingFeatureHeader() {
        testInvokeApiFeatureHeader(new ClientTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                List<Pair<String, String>> requestHeaders = new ArrayList<Pair<String, String>>();
                requestHeaders.add(new Pair<String, String>(HttpConstants.ContentType, "text/plain"));
                requestHeaders.add(new Pair<String, String>("X-ZUMO-FEATURES", "something"));
                byte[] content = "hello world".getBytes();
                client.invokeApi("apiName", content, HttpConstants.PostMethod, requestHeaders, queryParams).get();
            }

        }, "something");
    }

    private void testInvokeApiFeatureHeader(ClientTestOperation operation, final String expectedFeaturesHeader) {
        MobileServiceClient client = null;
        try {
            client = new MobileServiceClient(appUrl, getInstrumentation().getTargetContext());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        // Add a new filter to the client
        client = client.withFilter(new ServiceFilter() {

            @Override
            public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request,
                    NextServiceFilterCallback nextServiceFilterCallback) {

                final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
                String featuresHeaderName = "X-ZUMO-FEATURES";

                Headers headers = request.getHeaders();
                String features = null;
                for (int i = 0; i < headers.size(); i++) {
                    if (headers.name(i) == featuresHeaderName) {
                        features = headers.value(i);
                    }
                }

                if (features == null) {
                    resultFuture.setException(new Exception("No " + featuresHeaderName + " header on API call"));
                } else if (!features.equals(expectedFeaturesHeader)) {
                    resultFuture.setException(new Exception("Incorrect features header; expected "
                            + expectedFeaturesHeader + ", actual " + features));
                } else {
                    ServiceFilterResponseMock response = new ServiceFilterResponseMock();
                    response.setContent("{}");
                    resultFuture.set(response);
                }

                return resultFuture;
            }
        });

        try {
            operation.executeOperation(client);
        } catch (Exception exception) {
            Throwable ex = exception;
            while (ex instanceof ExecutionException || ex instanceof MobileServiceException) {
                ex = ex.getCause();
            }
            fail(ex.getMessage());
        }
    }

    public void testTypedSyncTablePullFeatureHeader() {
        // Both typed and untyped sync tables are treated as untyped
        // in the offline implementation
        testSyncTablePullOperationsFeatureHeader(new OfflineTableTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client,
                    MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonSyncTable jsonTable) throws Exception {
                ExecutableQuery<PersonTestObjectWithStringId> query = client
                        .getTable(PersonTestObjectWithStringId.class).where();
                typedTable.pull(query).get();
            }
        }, "OL,TU");
    }

    public void testJsonSyncTablePullFeatureHeader() {
        // Both typed and untyped sync tables are treated as untyped
        // in the offline implementation
        testSyncTablePullOperationsFeatureHeader(new OfflineTableTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client,
                    MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonSyncTable jsonTable) throws Exception {
                ExecutableJsonQuery query = client.getTable("Person").where();
                jsonTable.pull(query).get();
            }
        }, "OL,TU");
    }

    public void testTypedSyncTablePushFeatureHeader() {
        // Both typed and untyped sync tables are treated as untyped
        // in the offline implementation
        testSyncTableOperationsFeatureHeader(new OfflineTableTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client,
                    MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonSyncTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                typedTable.insert(pto).get();
                client.getSyncContext().push().get();
            }
        }, "OL,TU");
    }

    public void testJsonSyncTablePushFeatureHeader() {
        // Both typed and untyped sync tables are treated as untyped
        // in the offline implementation
        testSyncTableOperationsFeatureHeader(new OfflineTableTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client,
                    MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonSyncTable jsonTable) throws Exception {
                JsonObject obj = createJsonObject();
                jsonTable.insert(obj).get();
                client.getSyncContext().push().get();
            }
        }, "OL,TU");
    }

    public void testTypedSyncTablePushMultipleItemsFeatureHeader() {
        // Both typed and untyped sync tables are treated as untyped
        // in the offline implementation
        testSyncTableOperationsFeatureHeader(new OfflineTableTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client,
                    MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonSyncTable jsonTable) throws Exception {
                PersonTestObjectWithStringId pto = new PersonTestObjectWithStringId("John", "Doe", 33);
                typedTable.insert(pto).get();
                pto = new PersonTestObjectWithStringId("Jane", "Roe", 34);
                typedTable.insert(pto).get();
                client.getSyncContext().push().get();
            }
        }, "OL,TU");
    }

    public void testJsonSyncTablePushMultipleItemsFeatureHeader() {
        // Both typed and untyped sync tables are treated as untyped
        // in the offline implementation
        testSyncTableOperationsFeatureHeader(new OfflineTableTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client,
                    MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable,
                    MobileServiceJsonSyncTable jsonTable) throws Exception {
                JsonObject obj = createJsonObject();
                jsonTable.insert(obj).get();
                obj = createJsonObject();
                obj.remove("id");
                obj.addProperty("id", "another-id");
                jsonTable.insert(obj).get();
                client.getSyncContext().push().get();
            }
        }, "OL,TU");
    }

    private void testSyncTableOperationsFeatureHeader(OfflineTableTestOperation operation,
            final String expectedFeaturesHeader) {
        MobileServiceClient client = null;
        try {
            client = new MobileServiceClient(appUrl, getInstrumentation().getTargetContext());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        boolean fistPullPage = false;

        MobileServiceLocalStoreMock store = new MobileServiceLocalStoreMock();
        // Add a new filter to the client
        client = client.withFilter(new ServiceFilter() {

            @Override
            public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request,
                    NextServiceFilterCallback nextServiceFilterCallback) {

                final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
                String featuresHeaderName = "X-ZUMO-FEATURES";

                Headers headers = request.getHeaders();
                String features = null;
                for (int i = 0; i < headers.size(); i++) {
                    if (headers.name(i) == featuresHeaderName) {
                        features = headers.value(i);
                    }
                }

                if (features == null) {
                    resultFuture.setException(new Exception("No " + featuresHeaderName + " header on API call"));
                } else if (!features.equals(expectedFeaturesHeader)) {
                    resultFuture.setException(new Exception("Incorrect features header; expected "
                            + expectedFeaturesHeader + ", actual " + features));
                } else {
                    ServiceFilterResponseMock response = new ServiceFilterResponseMock();
                    Uri requestUri = Uri.parse(request.getUrl());

                    String content = "[]";

                    //if (fistPullPage) {
                    content = "{\"id\":\"the-id\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":33}";

                    if (request.getMethod().equalsIgnoreCase(HttpConstants.GetMethod)
                            && requestUri.getPathSegments().size() == 2) {
                        // GET which should return an array of results
                        content = "[" + content + "]";
                    }

                    //fistPullPage = false;
                    //}

                    response.setContent(content);
                    resultFuture.set(response);
                }

                return resultFuture;
            }
        });

        try {
            Map<String, ColumnDataType> tableDefinition = new HashMap<String, ColumnDataType>();
            tableDefinition.put("id", ColumnDataType.String);
            tableDefinition.put("firstName", ColumnDataType.String);
            tableDefinition.put("lastName", ColumnDataType.String);
            tableDefinition.put("age", ColumnDataType.Integer);
            store.defineTable("Person", tableDefinition);

            client.getSyncContext().initialize(store, new SimpleSyncHandler()).get();

            MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable = client
                    .getSyncTable(PersonTestObjectWithStringId.class);
            MobileServiceJsonSyncTable jsonTable = client.getSyncTable("Person");
            operation.executeOperation(client, typedTable, jsonTable);
        } catch (Exception exception) {
            Throwable ex = exception;
            while (ex instanceof ExecutionException || ex instanceof MobileServiceException) {
                ex = ex.getCause();
            }
            fail(ex.getMessage());
        }
    }

    private void testSyncTablePullOperationsFeatureHeader(OfflineTableTestOperation operation,
            final String expectedFeaturesHeader) {
        MobileServiceClient client = null;
        try {
            client = new MobileServiceClient(appUrl, getInstrumentation().getTargetContext());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        MobileServiceLocalStoreMock store = new MobileServiceLocalStoreMock();
        // Add a new filter to the client
        client = client.withFilter(new ServiceFilter() {

            @Override
            public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request,
                    NextServiceFilterCallback nextServiceFilterCallback) {

                boolean isFirstPage = request.getUrl().contains("$skip=0");

                final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
                String featuresHeaderName = "X-ZUMO-FEATURES";

                Headers headers = request.getHeaders();
                String features = null;
                for (int i = 0; i < headers.size(); i++) {
                    if (headers.name(i) == featuresHeaderName) {
                        features = headers.value(i);
                    }
                }

                if (features == null) {
                    resultFuture.setException(new Exception("No " + featuresHeaderName + " header on API call"));
                } else if (!features.equals(expectedFeaturesHeader)) {
                    resultFuture.setException(new Exception("Incorrect features header; expected "
                            + expectedFeaturesHeader + ", actual " + features));
                } else {
                    ServiceFilterResponseMock response = new ServiceFilterResponseMock();
                    Uri requestUri = Uri.parse(request.getUrl());

                    String content = "[]";

                    if (isFirstPage) {
                        content = "{\"id\":\"the-id\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":33}";

                        if (request.getMethod().equalsIgnoreCase(HttpConstants.GetMethod)
                                && requestUri.getPathSegments().size() == 2) {
                            // GET which should return an array of results
                            content = "[" + content + "]";
                        }

                    }

                    response.setContent(content);
                    resultFuture.set(response);
                }

                return resultFuture;
            }
        });

        try {
            Map<String, ColumnDataType> tableDefinition = new HashMap<String, ColumnDataType>();
            tableDefinition.put("id", ColumnDataType.String);
            tableDefinition.put("firstName", ColumnDataType.String);
            tableDefinition.put("lastName", ColumnDataType.String);
            tableDefinition.put("age", ColumnDataType.Integer);
            store.defineTable("Person", tableDefinition);

            client.getSyncContext().initialize(store, new SimpleSyncHandler()).get();

            MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable = client
                    .getSyncTable(PersonTestObjectWithStringId.class);
            MobileServiceJsonSyncTable jsonTable = client.getSyncTable("Person");
            operation.executeOperation(client, typedTable, jsonTable);
        } catch (Exception exception) {
            Throwable ex = exception;
            while (ex instanceof ExecutionException || ex instanceof MobileServiceException) {
                ex = ex.getCause();
            }
            fail(ex.getMessage());
        }
    }

    public void testJsonTableUpdateWithVersionFeatureHeader() {
        testOpportunisticConcurrencyOperationsFeatureHeader(new OpportunisticConcurrencyTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                MobileServiceJsonTable jsonTable = client.getTable("Person");
                JsonObject jo = createJsonObject();
                jo.addProperty("version", "abc");
                List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
                queryParams.add(new Pair<String, String>("a", "b"));
                jsonTable.update(jo, queryParams).get();
            }
        }, "OC,QS,TU");
    }

    public void testTypedTableUpdateWithVersionFeatureHeader() {
        testOpportunisticConcurrencyOperationsFeatureHeader(new OpportunisticConcurrencyTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                MobileServiceTable<VersionType> table = client.getTable(VersionType.class);
                VersionType obj = new VersionType();
                obj.Version = "abc";
                obj.Id = "the-id";
                table.update(obj).get();
            }
        }, "OC,TT");
    }

    public void testOfflinePushWithVersionFeatureHeader() {
        testOpportunisticConcurrencyOperationsFeatureHeader(new OpportunisticConcurrencyTestOperation() {

            @Override
            public void executeOperation(MobileServiceClient client) throws Exception {
                MobileServiceSyncTable<VersionType> table = client.getSyncTable(VersionType.class);
                VersionType obj = new VersionType();
                obj.Version = "abc";
                obj.Id = "the-id";
                table.update(obj).get();
                client.getSyncContext().push().get();
            }
        }, "OC,OL,TU");
    }

    private void testOpportunisticConcurrencyOperationsFeatureHeader(
            OpportunisticConcurrencyTestOperation operation, final String expectedFeaturesHeader) {
        MobileServiceClient client = null;
        try {
            client = new MobileServiceClient(appUrl, getInstrumentation().getTargetContext());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        MobileServiceLocalStoreMock store = new MobileServiceLocalStoreMock();
        // Add a new filter to the client
        client = client.withFilter(new ServiceFilter() {

            @Override
            public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request,
                    NextServiceFilterCallback nextServiceFilterCallback) {

                final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
                String featuresHeaderName = "X-ZUMO-FEATURES";

                Headers headers = request.getHeaders();
                String features = null;
                for (int i = 0; i < headers.size(); i++) {
                    if (headers.name(i) == featuresHeaderName) {
                        features = headers.value(i);
                    }
                }

                if (features == null) {
                    resultFuture.setException(new Exception("No " + featuresHeaderName + " header on API call"));
                } else if (!features.equals(expectedFeaturesHeader)) {
                    resultFuture.setException(new Exception("Incorrect features header; expected "
                            + expectedFeaturesHeader + ", actual " + features));
                } else {
                    ServiceFilterResponseMock response = new ServiceFilterResponseMock();
                    Uri requestUri = Uri.parse(request.getUrl());
                    String content = "{\"id\":\"the-id\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":33}";
                    if (request.getMethod().equalsIgnoreCase(HttpConstants.GetMethod)
                            && requestUri.getPathSegments().size() == 2) {
                        // GET which should return an array of results
                        content = "[" + content + "]";
                    }
                    response.setContent(content);
                    resultFuture.set(response);
                }

                return resultFuture;
            }
        });

        try {
            Map<String, ColumnDataType> tableDefinition = new HashMap<String, ColumnDataType>();
            tableDefinition.put("id", ColumnDataType.String);
            tableDefinition.put("firstName", ColumnDataType.String);
            tableDefinition.put("lastName", ColumnDataType.String);
            tableDefinition.put("age", ColumnDataType.Integer);
            store.defineTable("Person", tableDefinition);

            client.getSyncContext().initialize(store, new SimpleSyncHandler()).get();

            operation.executeOperation(client);
        } catch (Exception exception) {
            Throwable ex = exception;
            while (ex instanceof ExecutionException || ex instanceof MobileServiceException) {
                ex = ex.getCause();
            }
            fail(ex.getMessage());
        }
    }

    // Tests for tables
    interface TableTestOperation {
        void executeOperation(MobileServiceTable<PersonTestObjectWithStringId> typedTable,
                MobileServiceJsonTable jsonTable) throws Exception;
    }

    // Tests for custom APIs
    interface ClientTestOperation {
        void executeOperation(MobileServiceClient client) throws Exception;
    }

    // Tests for offline push / pull
    interface OfflineTableTestOperation {
        void executeOperation(MobileServiceClient client,
                MobileServiceSyncTable<PersonTestObjectWithStringId> typedTable,
                MobileServiceJsonSyncTable jsonTable) throws Exception;
    }

    // Tests for opportunistic concurrency (conditional updates)
    interface OpportunisticConcurrencyTestOperation {
        void executeOperation(MobileServiceClient client) throws Exception;
    }
}