You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2014/08/28 05:48:14 UTC

[07/51] [partial] rename folder /datajs into /odatajs. no file modification.

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/d5ec5557/odatajs/tests/odata-cache-functional-tests.js
----------------------------------------------------------------------
diff --git a/odatajs/tests/odata-cache-functional-tests.js b/odatajs/tests/odata-cache-functional-tests.js
new file mode 100644
index 0000000..ac63460
--- /dev/null
+++ b/odatajs/tests/odata-cache-functional-tests.js
@@ -0,0 +1,631 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, */*;q=0.1";
+    var CustomDataSource = function (baseUri) {
+        this.baseUri = baseUri;
+    };
+
+    CustomDataSource.prototype.read = function (index, count, success, error) {
+        var that = this;
+        var url = this.baseUri + "?$skip=" + index + "&$top=" + count;
+        $(this).triggerHandler("request", { requestUri: url });
+        $.ajax({
+            url: url,
+            dataType: "json",
+            success: function (results) {
+                $(that).triggerHandler("success", { data: results });
+                success(results);
+            },
+            error: error
+        });
+    };
+
+    CustomDataSource.prototype.count = function (success, error) {
+        $.ajax({
+            url: "./endpoints/CustomDataService.svc/Count",
+            dataType: "json",
+            success: success,
+            error: error
+        });
+    },
+    CustomDataSource.prototype.toString = function () {
+        return this.baseUri;
+    };
+
+    var sources = [
+        { source: "./endpoints/FoodStoreDataServiceV4.svc/Foods", countSupported: true },
+        { source: new CustomDataSource("./endpoints/CustomDataService.svc/ReadRange"), countSupported: true }
+    ];
+
+    var itemsInCollection = 16;
+
+    // Cache config variations for single readRange with fixed skip/take that spans the entire collection
+    var pageSizes = [
+        1,
+        4,  // factor of total, <= server page size
+        5,  // non-factor of total, <= server page size
+        6,  // non-factor of total, > server page size
+        8,  // factor of total, > server page size
+        itemsInCollection,
+        itemsInCollection + 1
+    ];
+
+    var cacheSizes = [
+        -5,           // All you can store 
+         0,           // Store nothing
+         1024,        // 1 KB
+         2.5 * 1024,  // 2.5 KB
+         100 * 1024,  // 10 KB
+         512 * 1024,  // 512 KB
+         100 * 1024 * 1024, // 100 MB
+         undefined    // Default to 1 MB
+    ];
+
+    // Skip/take variations for single readRange with fixed cache config
+    var fixedPageSize = 6;
+
+    var skipValues = [
+        0,
+        fixedPageSize - 1,
+        fixedPageSize,
+        fixedPageSize + 1,
+        fixedPageSize * 2,
+        itemsInCollection + 1,
+        itemsInCollection + fixedPageSize + 1
+    ];
+
+    var takeValues = [
+        0,
+        fixedPageSize - 1,
+        fixedPageSize,
+        fixedPageSize + 1,
+        fixedPageSize * 2,
+        itemsInCollection + 1
+    ];
+
+    // Skip/take variations for multiple readRanges with fixed cache config
+    var multipleReads = [
+        { skip: 1, take: 2 },   // page 1
+        {skip: 2, take: 7 },   // page 1, 2
+        {skip: 3, take: 10 },  // page 1, 2, 3
+        {skip: 6, take: 2}    // page 2
+    ];
+
+    var invalidSkipValues = [-5, NaN, undefined, Infinity, "not a valid value"];
+    var invalidTakeValues = [-5, NaN, undefined, Infinity, "not a valid value"];
+    var invalidPageSizes = [-5, NaN, Infinity, 0, "not a valid value"];
+
+    // Prefetchsize variations for a single readRange
+    var prefetchSizes = [
+        -5,
+        1,
+        5,
+        itemsInCollection,
+        itemsInCollection + 1
+    ];
+
+    var expectException = function (cache) {
+        djstest.assert(false, "We should not get here because the an exception is expected.");
+        djstest.destroyCacheAndDone(cache);
+    };
+
+    var makeUnexpectedErrorHandler = function (cache) {
+        return function (err) {
+            djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+            if (cache) {
+                djstest.destroyCacheAndDone(cache);
+            } else {
+                djstest.done();
+            }
+        };
+    };
+
+    var validateExpectedRange = function (cache, data, source, skipValue, takeValue, finished) {
+        /** Validates the data returned by readRange
+         * @param {Object} cache - The cache object
+         * @param {Object} data - The data returned by the cache
+         * @param {Object} source - The base URI of the feed, or the custom data source
+         * @param {Integer} skipValue - The skip value
+         * @param {Integer} takeValue - The take value
+         * @param {Function} finished - Callback function called after data is verified
+         */
+        var assertData = function (expectedData) {
+            djstest.assertAreEqualDeep(data, expectedData, "Verify response data");
+            finished();
+        };
+
+        if (typeof source === "string") {
+            var expectedRangeUrl = source + "?$skip=" + skipValue + "&$top=" + takeValue;
+            window.ODataReadOracle.readJsonAcrossServerPages(expectedRangeUrl, assertData);
+        } else {
+            source.read(skipValue, takeValue, assertData);
+        }
+    };
+
+    var onidleValidation = function () {
+        djstest.assert(true, "expected call to onidle");
+        djstest.done();
+    };
+
+    var createSingleReadTestName = function (params) {
+        return "Testing readRange of " + params.source + " [skip " + params.skip + " take " + params.take + "] with pageSize " +
+            params.pageSize + ", prefetch " + params.prefetchSize + " and cacheSize " + params.cacheSize;
+    };
+
+    var createMultipleReadTestName = function (params) {
+        return "Testing readRange of " + params.source + " [skip " + params.firstSkip + " take " + params.firstTake + ", " +
+            (params.destroyCacheBetweenReads ? "cache.destroy(), " : "") +
+            "skip " + params.secondSkip + " take " + params.secondTake + "] with pageSize " +
+            params.pageSize + ", prefetch " + params.prefetchSize + " and cacheSize " + params.cacheSize;
+    };
+
+    var dataCacheSingleReadRangeTest = function (params) {
+        djstest.assertsExpected(2);
+        var options = { name: "cache" + new Date().valueOf(), source: params.source, pageSize: params.pageSize, prefetchSize: params.prefetchSize, cacheSize: params.cacheSize };
+
+        if (params.mechanism) {
+            options.mechanism = params.mechanism;
+        }
+
+        var cache = this.createAndAddCache(options);
+        var session = typeof params.source === "string" ? this.observableHttpClient.newSession() : new Session(params.source);
+        var cacheOracle = new CacheOracle(params.source, params.pageSize, itemsInCollection, params.cacheSize);
+        cache.readRange(params.skip, params.take).then(function (data) {
+            cacheOracle.verifyRequests(session.requests, session.responses, params.skip, params.take, "readRange requests");
+            validateExpectedRange(cache, data, params.source, params.skip, params.take, function () {
+                djstest.destroyCacheAndDone(cache);
+            });
+        }, makeUnexpectedErrorHandler(cache));
+    };
+
+    var dataCacheParallelReadRangeTest = function (params) {
+        djstest.assertsExpected(2);
+        var options = { name: "cache" + new Date().valueOf(), source: params.source, pageSize: params.pageSize, prefetchSize: params.prefetchSize, cacheSize: params.cacheSize };
+
+        var cache = this.createAndAddCache(options);
+
+        var firstReadRange = function (finished) {
+            cache.readRange(params.firstSkip, params.firstTake).then(function (data) {
+                validateExpectedRange(cache, data, params.source, params.firstSkip, params.firstTake, finished);
+            }, makeUnexpectedErrorHandler(cache));
+        };
+
+        var secondReadRange = function (finished) {
+            cache.readRange(params.secondSkip, params.secondTake).then(function (data) {
+                validateExpectedRange(cache, data, params.source, params.secondSkip, params.secondTake, finished);
+            }, makeUnexpectedErrorHandler(cache));
+        };
+
+        djstest.asyncDo([firstReadRange, secondReadRange], function () {
+            djstest.destroyCacheAndDone(cache);
+        });
+    };
+
+    var dataCacheSerialReadRangeTest = function (params) {
+        djstest.assertsExpected(4);
+        var options = { name: "cache" + new Date().valueOf(), source: params.source, pageSize: params.pageSize, prefetchSize: params.prefetchSize, cacheSize: params.cacheSize };
+
+        var cacheOracle = new CacheOracle(params.source, params.pageSize, itemsInCollection, params.cacheSize);
+        var secondRead = function () {
+            session.clear();
+            cache.readRange(params.secondSkip, params.secondTake).then(function (data) {
+                cacheOracle.verifyRequests(session.requests, session.responses, params.secondSkip, params.secondTake, "Second readRange requests");
+                validateExpectedRange(cache, data, params.source, params.secondSkip, params.secondTake, djstest.done);
+            }, makeUnexpectedErrorHandler(cache));
+        };
+
+        var cache = this.createAndAddCache(options);
+        var session = typeof params.source === "string" ? this.observableHttpClient.newSession() : new Session(params.source);
+        cache.readRange(params.firstSkip, params.firstTake).then(function (data) {
+            cacheOracle.verifyRequests(session.requests, session.responses, params.firstSkip, params.firstTake, "First readRange requests");
+            validateExpectedRange(cache, data, params.source, params.firstSkip, params.firstTake, function () {
+                if (params.destroyCacheBetweenReads === true) {
+                    cache.clear().then(function () {
+                        cacheOracle.clear();
+                        secondRead();
+                    }, function (err) {
+                        djstest.fail("Error destroying the cache: " + djstest.toString(err));
+                    });
+                } else {
+                    secondRead();
+                }
+            });
+        }, makeUnexpectedErrorHandler(cache));
+    };
+
+    var dataCachePrefetchTest = function (params) {
+        djstest.assertsExpected(2);
+        var options = {
+            name: "cache" + new Date().valueOf(),
+            source: params.source,
+            pageSize: params.pageSize,
+            prefetchSize: params.prefetchSize,
+            cacheSize: params.cacheSize,
+            user: params.user,
+            password: params.password
+        };
+
+        var cache = this.createAndAddCache(options);
+        var session = typeof params.source === "string" ? this.observableHttpClient.newSession() : new Session(params.source);
+        var cacheOracle = new CacheOracle(params.source, params.pageSize, itemsInCollection, params.cacheSize);
+
+        cache.readRange(params.skip, params.take).then(function (data) {
+            cacheOracle.verifyRequests(session.requests, session.responses, params.skip, params.take, "readRange requests");
+            session.clear();
+        }, makeUnexpectedErrorHandler(cache));
+
+        cache.onidle = function () {
+            var prefetchSize = params.prefetchSize < 0 ? itemsInCollection : params.prefetchSize;
+            cacheOracle.verifyRequests(session.requests, session.responses, params.skip + params.take, prefetchSize, "prefetch requests", false, true);
+            cache.onidle = false;
+            djstest.destroyCacheAndDone(cache);
+        };
+    };
+
+    var cleanDomStorage = function (done) {
+        /** Cleans all the data saved in the browser's DOM Storage. Needs to be called asynchronously in the 
+         * setup and teardown methods to be consistent with indexedDb's cleanup method.
+         * @param {Function} done - Function to be called after DOM storage is cleared.
+         */
+                 if (window.localStorage) {
+            window.localStorage.clear();
+        }
+        done();
+    };
+
+    var cleanIndexedDb = function (done) {
+        /** Cleans all the data saved in the browser's IndexedDb Storage.
+         * @param {Function} done - Function to be called after indexedDb is cleared.
+         */
+        var caches = this.caches;
+
+        djstest.cleanStoreOnIndexedDb(caches, done);
+    };
+
+    var cleanupAllStorage = function (done) {
+        /** Cleans up all available storage mechanisms in the browser.
+         * @param {Function} done - Function to be called by each cleanup function after storage is cleared.
+         */
+        var that = this;
+        var storeCleanup = [];
+
+        $.each(CacheOracle.mechanisms, function (_, mechanism) {
+            if (CacheOracle.isMechanismAvailable(mechanism)) {
+                storeCleanup.push(function (done) {
+                    if (storageMechanisms[mechanism]) {
+                        storageMechanisms[mechanism].cleanup.call(that, done);
+                    } else {
+                        done();
+                    }
+                });
+            }
+        });
+
+        djstest.asyncDo(storeCleanup, done);
+    };
+
+    var storageMechanisms = {
+        indexeddb: { cleanup: cleanIndexedDb },
+        dom: { cleanup: cleanDomStorage },
+        memory: { cleanup: function (done) { done(); } },
+        best: { cleanup: function (done) { done(); } }
+    };
+
+    module("Functional", {
+        setup: function () {
+            this.createAndAddCache = function (options) {
+                /** Returns a cache created from the options object and 
+                 * @param {Object} options - Object to create a cache from. 
+                 */
+                var cache = odatajs.cache.createDataCache(options);
+                this.caches.push({ name: options.name, cache: cache });
+                return cache;
+            };
+
+            this.observableHttpClient = new ObservableHttpClient();
+            window.odatajs.oData.net.defaultHttpClient = this.observableHttpClient;
+            this.caches = [];
+            var that = this;
+
+            djstest.wait(function (done) {
+                cleanupAllStorage.call(that, done);
+            });
+        },
+
+        teardown: function () {
+            window.odatajs.oData.net.defaultHttpClient = this.observableHttpClient.provider;
+            var clearActions = [];
+            var that = this;
+
+            $.each(this.caches, function (_, cacheObject) {
+                cacheObject.cache.onidle = undefined;
+
+                clearActions.push(function (done) {
+                    cacheObject.cache.clear().then(function () {
+                        done();
+                    },
+                        function (err) {
+                            djstest.assert(false, "Unexpected call to error handler while attempting to clear with error: " + djstest.toString(err));
+                        })
+                });
+            });
+
+            djstest.wait(function (done) {
+                djstest.asyncDo(clearActions, function () {
+                    cleanupAllStorage.call(that, function () {
+                        that.caches = [];
+                        done();
+                    });
+                });
+            });
+        }
+    });
+
+    $.each(CacheOracle.mechanisms, function (_, mechanism) {
+        var parameters = { mechanism: mechanism, source: sources[1].source, take: 5, skip: 0, pageSize: 5, prefetchSize: 5 };
+        if (CacheOracle.isMechanismAvailable(mechanism)) {
+            djstest.addTest(dataCacheSingleReadRangeTest, "Specified mechanism: " + parameters.mechanism + createSingleReadTestName(parameters), parameters);
+        }
+        else {
+            djstest.addTest(function (params) {
+                djstest.assertsExpected(1);
+                var options = { name: "cache" + new Date().valueOf(), mechanism: params.mechanism, source: params.source };
+                try {
+                    var cache = this.createAndAddCache(options);
+                    expectException(cache);
+                }
+                catch (e) {
+                    if (mechanism === "indexeddb") {
+                        djstest.assertAreEqual(e.message, "IndexedDB is not supported on this browser", "Validating expected error");
+                    } else if (mechanism === "dom") {
+                        djstest.assertAreEqual(e.message, "Web Storage not supported by the browser", "Validating expected error");
+                    } else {
+                        djstest.fail("Creating cache with mechanism " + mechanism + " should not fail: " + djstest.toString(e));
+                    }
+
+                    djstest.done();
+                }
+            }, "Invalid mechanism for browser: " + parameters.mechanism + createSingleReadTestName(parameters), parameters);
+        }
+    });
+
+    $.each(sources, function (_, sourceObject) {
+        $.each(pageSizes, function (_, pageSizeValue) {
+            $.each(cacheSizes, function (_, cacheSizeValue) {
+                var parameters = { source: sourceObject.source, skip: 0, take: itemsInCollection, pageSize: pageSizeValue, prefetchSize: 0, cacheSize: cacheSizeValue };
+                djstest.addTest(dataCacheSingleReadRangeTest, createSingleReadTestName(parameters), parameters);
+            });
+        });
+
+        $.each(skipValues, function (_, skipValue) {
+            $.each(takeValues, function (_, takeValue) {
+                var parameters = { source: sourceObject.source, take: takeValue, skip: skipValue, pageSize: 4, prefetchSize: 0, cacheSize: 0 };
+                djstest.addTest(dataCacheSingleReadRangeTest, createSingleReadTestName(parameters), parameters);
+            });
+        });
+
+        $.each(multipleReads, function (_, firstRange) {
+            $.each(multipleReads, function (_, secondRange) {
+                var parallelReadParams = { source: sourceObject.source, firstTake: firstRange.take, firstSkip: firstRange.skip, secondTake: secondRange.take, secondSkip: secondRange.skip, pageSize: 5, prefetchSize: 0, cacheSize: 0 };
+                djstest.addTest(dataCacheParallelReadRangeTest, "Parallel: " + createMultipleReadTestName(parallelReadParams), parallelReadParams);
+
+                $.each([false, true], function (_, destroyCacheBetweenReads) {
+                    var serialReadParams = $.extend({}, parallelReadParams, { destroyCacheBetweenReads: destroyCacheBetweenReads });
+                    djstest.addTest(dataCacheSerialReadRangeTest, "Serial: " + createMultipleReadTestName(serialReadParams), serialReadParams);
+                });
+            });
+        });
+
+        var getInvalidValueErrorMessage = function (invalidValue, parameterName) {
+            /** Returns the expected error message for the specified invalid value.
+             * @param {Object} invalidValue - invalid value (anything other than zero or positive integer) to determine the error message from.
+             * @param {String} parameterName - The name of the parameter being verified.
+             * @returns {String} Error message expected.
+             */
+            return (invalidValue === undefined || typeof invalidValue !== "number") ?
+                        "'" + parameterName + "' must be a number." :
+                        "'" + parameterName + "' must be greater than or equal to zero.";
+        };
+
+        $.each(invalidSkipValues, function (_, invalidSkipValue) {
+            var parameters = { source: sourceObject.source, skip: invalidSkipValue, take: 1 };
+            djstest.addTest(function (params) {
+                djstest.assertsExpected(1);
+                var options = { name: "cache" + new Date().valueOf(), source: params.source };
+                var cache = this.createAndAddCache(options);
+                try {
+                    cache.readRange(params.skip, params.take);
+                    expectException(cache);
+                } catch (e) {
+                    djstest.assertAreEqual(e.message, getInvalidValueErrorMessage(invalidSkipValue, "index"), "Error message validation");
+                    djstest.destroyCacheAndDone(cache);
+                }
+            }, "Invalid skip: " + createSingleReadTestName(parameters), parameters);
+        });
+
+        $.each(invalidTakeValues, function (_, invalidTakeValue) {
+            var parameters = { source: sourceObject.source, skip: 0, take: invalidTakeValue };
+            djstest.addTest(function (params) {
+                djstest.assertsExpected(1);
+                var options = { name: "cache" + new Date().valueOf(), source: params.source };
+                var cache = this.createAndAddCache(options);
+                try {
+                    cache.readRange(params.skip, params.take);
+                    expectException(cache);
+                } catch (e) {
+                    djstest.assertAreEqual(e.message, getInvalidValueErrorMessage(invalidTakeValue, "count"), "Error message validation");
+                    djstest.destroyCacheAndDone(cache);
+                }
+            }, "Invalid take: " + createSingleReadTestName(parameters), parameters);
+        });
+
+        $.each(invalidPageSizes, function (_, invalidPageSize) {
+            var parameters = { source: sourceObject.source, skip: 0, take: 5, pageSize: invalidPageSize };
+            djstest.addTest(function (params) {
+                var options = { name: "cache", source: params.source, pageSize: params.pageSize };
+                try {
+                    var cache = this.createAndAddCache(options);
+                    expectException(cache);
+                } catch (e) {
+                    var expectedError = typeof invalidPageSize === "number" ? "'pageSize' must be greater than zero." : getInvalidValueErrorMessage(invalidPageSize, "pageSize");
+                    djstest.assertAreEqual(e.message, expectedError, "Error message validation");
+                }
+                djstest.done();
+            }, "Invalid pageSize: " + createSingleReadTestName(parameters), parameters);
+        });
+
+        $.each(pageSizes, function (_, pageSize) {
+            $.each(cacheSizes, function (_, cacheSize) {
+                var parameters = { source: sourceObject.source, skip: 0, take: pageSize, pageSize: pageSize, prefetchSize: -1, cacheSize: cacheSize };
+                djstest.addTest(dataCachePrefetchTest, "Prefetch: " + createSingleReadTestName(parameters), parameters);
+            });
+        });
+
+        $.each(skipValues, function (_, skipValue) {
+            var parameters = { source: sourceObject.source, skip: skipValue, take: fixedPageSize, pageSize: fixedPageSize, prefetchSize: -1, cacheSize: -1 };
+            djstest.addTest(dataCachePrefetchTest, "Prefetch: " + createSingleReadTestName(parameters), parameters);
+        });
+
+        $.each(takeValues, function (_, takeValue) {
+            var parameters = { source: sourceObject.source, skip: 0, take: takeValue, pageSize: fixedPageSize, prefetchSize: -1, cacheSize: -1 };
+            djstest.addTest(dataCachePrefetchTest, "Prefetch: " + createSingleReadTestName(parameters), parameters);
+        });
+
+        $.each(prefetchSizes, function (_, prefetchSize) {
+            var parameters = { source: sourceObject.source, skip: 0, take: fixedPageSize, pageSize: fixedPageSize, prefetchSize: prefetchSize, cacheSize: -1 };
+            djstest.addTest(dataCachePrefetchTest, "Prefetch: " + createSingleReadTestName(parameters), parameters);
+        });
+
+        var fixedPrefetchSize = 5;
+
+        djstest.addTest(function (params) {
+            djstest.assertsExpected(1);
+            var cache = this.createAndAddCache({
+                name: "cache" + new Date().valueOf(),
+                source: params.source,
+                prefetchSize: fixedPrefetchSize,
+                idle: onidleValidation,
+                pageSize: 2,
+                mechanism: "memory"
+            });
+            cache.readRange(0, 5);
+
+        }, "onidle in constructor, prefetch size = " + fixedPrefetchSize + " on " + sourceObject.source, { source: sourceObject.source });
+
+        djstest.addTest(function (params) {
+            djstest.assertsExpected(1);
+            var cache = this.createAndAddCache({
+                name: "cache" + new Date().valueOf(),
+                source: params.source,
+                prefetchSize: fixedPrefetchSize,
+                pageSize: 2,
+                mechanism: "memory"
+            });
+            cache.onidle = onidleValidation;
+            cache.readRange(0, 5);
+        }, "onidle, prefetch size = " + fixedPrefetchSize + " on " + sourceObject.source, { source: sourceObject.source });
+
+        djstest.addTest(function (params) {
+            djstest.assertsExpected(1);
+            var cache = this.createAndAddCache({
+                name: "cache" + new Date().valueOf(),
+                source: params.source,
+                prefetchSize: fixedPrefetchSize,
+                idle: function () { djstest.assert(false, "unexpected onidle call") },
+                pageSize: 2,
+                mechanism: "memory"
+            });
+            cache.onidle = onidleValidation;
+            cache.readRange(0, 5);
+        }, "onidle override, prefetch size = " + fixedPrefetchSize + " on " + sourceObject.source, { source: sourceObject.source });
+
+        djstest.addTest(function (params) {
+            var cache = this.createAndAddCache({ name: "cache" + new Date().valueOf(), source: params.source, pageSize: 1, prefetchSize: -1 });
+            var observableSource = typeof params.source === "string" ? this.observableHttpClient : params.source;
+            cache.readRange(0, 1).then(function (data) {
+                // Let one prefetch request go out, to make sure the prefetcher is started, and then destroy the cache
+                $(observableSource).one("success", function () {
+                    var session = new Session(observableSource);
+                    cache.clear().then(function () {
+                        setTimeout(function () {
+                            djstest.assertAreEqualDeep(session.requests, [], "Verify no prefetch requests are sent out after cache.clear() callback");
+                            djstest.done();
+                        }, 1000);
+                    }, function (err) {
+                        djstest.fail("Error destroying the cache: " + djstest.toString(err));
+                    });
+                });
+            });
+        }, "Testing cache.clear() halts the prefetcher" + sourceObject.source, { source: sourceObject.source });
+
+        djstest.addTest(function (params) {
+            var cache = this.createAndAddCache({ name: "cache" + new Date().valueOf(), source: params.source });
+            if (params.countSupported) {
+                cache.count().then(function (count) {
+                    djstest.assertAreEqual(count, itemsInCollection, "All items accounted for");
+                    djstest.destroyCacheAndDone(cache);
+                }, makeUnexpectedErrorHandler(cache));
+            }
+            else {
+                cache.count().then(function (count) {
+                    djstest.assert(false, "Success should not be called, count not supported");
+                    djstest.destroyCacheAndDone(cache);
+                }, function (err) {
+                    djstest.assertAreEqual(err.message, "HTTP request failed", "Validating expected error");
+                    djstest.destroyCacheAndDone(cache);
+                });
+            }
+        }, "Testing cache.count() on " + sourceObject.source, { source: sourceObject.source, countSupported: sourceObject.countSupported });
+
+        djstest.addTest(function (params) {
+            var cache = this.createAndAddCache({ name: "cache" + new Date().valueOf(), source: params.source, pageSize: 1, prefetchSize: 0 });
+            var session = typeof params.source === "string" ? this.observableHttpClient.newSession() : new Session(params.source);
+            cache.readRange(0, 1).cancel();
+            setTimeout(function () {
+                djstest.assertAreEqualDeep(session.requests, [], "Verify no requests are sent out after readRange is cancelled");
+                djstest.done();
+            }, 1000);
+        }, "Testing cancel()" + sourceObject.source, { source: sourceObject.source });
+
+        djstest.addTest(function (params) {
+            djstest.assertsExpected(1);
+            var cache = this.createAndAddCache({ name: "cache" + new Date().valueOf(), source: params.source, pageSize: 1, prefetchSize: 0 });
+            cache.clear().then(function () {
+                cache.clear().then(function () {
+                    djstest.pass("Second clear succeeded");
+                    djstest.done();
+                }, makeUnexpectedErrorHandler(cache));
+            }, makeUnexpectedErrorHandler(cache));
+        }, "Testing .clear().then(cache.clear())" + sourceObject.source, { source: sourceObject.source });
+    });
+
+    var params = {
+        source: "./endpoints/BasicAuthDataService.svc/Customers",
+        skip: 0,
+        take: 5,
+        pageSize: 5,
+        prefetchSize: -1,
+        cacheSize: -1,
+        user: "djsUser",
+        password: "djsPassword"
+    };
+    djstest.addTest(dataCachePrefetchTest, createSingleReadTestName(params), params);
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/d5ec5557/odatajs/tests/odata-cache-rx-functional-tests.html
----------------------------------------------------------------------
diff --git a/odatajs/tests/odata-cache-rx-functional-tests.html b/odatajs/tests/odata-cache-rx-functional-tests.html
new file mode 100644
index 0000000..aab512c
--- /dev/null
+++ b/odatajs/tests/odata-cache-rx-functional-tests.html
@@ -0,0 +1,50 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+ -->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>>datajs.cache toObservable() tests</title>
+    <meta http-equiv="cache-control" content="no-cache"/> 
+    <meta http-equiv="pragma" content="no-cache"/> 
+    <meta http-equiv="expires" content="-1"/> 
+
+    <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.10.0.css" type="text/css" />
+    <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
+    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.min.js"></script>
+    <script type="text/javascript" src="http://code.jquery.com/qunit/qunit-1.10.0.js"></script>
+    <script type="text/javascript" src="common/TestSynchronizerClient.js"></script>
+    <script type="text/javascript">
+        window.TestSynchronizer.init(QUnit);
+    </script>
+    <script type="text/javascript" src="../build/odatajs-4.0.0-beta-01.js"></script>   
+    <script type="text/javascript" src="common/common.js"></script>
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="common/ODataReadOracle.js"></script>
+    <script type="text/javascript" src="common/rx.js"></script>
+    <script type="text/javascript" src="odata-cache-rx-functional-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">datajs.cache toObservable() tests</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/d5ec5557/odatajs/tests/odata-cache-rx-functional-tests.js
----------------------------------------------------------------------
diff --git a/odatajs/tests/odata-cache-rx-functional-tests.js b/odatajs/tests/odata-cache-rx-functional-tests.js
new file mode 100644
index 0000000..453e586
--- /dev/null
+++ b/odatajs/tests/odata-cache-rx-functional-tests.js
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, */*;q=0.1";
+    var feeds = [
+        { uri: "./endpoints/FoodStoreDataServiceV4.svc/Foods" }
+    ];
+
+    var itemsInCollection = 16;
+    var pageSizes = [
+        1,
+        4,  // factor of total, <= server page size
+        5,  // non-factor of total, <= server page size
+        6,  // non-factor of total, > server page size
+        8,  // factor of total, > server page size
+        itemsInCollection,
+        itemsInCollection + 1
+    ];
+
+    var operatorTests = [
+        function (observable) { return observable.Take(0); },
+        function (observable) { return observable.Take(1); },
+        function (observable) { return observable.Skip(1); },
+        function (observable) { return observable.Skip(2).Take(4); },
+        function (observable) { return observable.Select(function (item) { return item.Name; }); },
+        function (observable) { return observable.Where(function (item) { return item.FoodID % 2 === 1; }); }
+    ];
+
+    var assertObservables = function (actual, expected, done) {
+        /** Asserts two finite observables generate the same sequence
+         * @param {IObservable} actual - The actual observable
+         * @param {IObservable} expected - The expected observable
+         * @param {Function} done - The callback function when asserts are done
+         */
+        var toArray = function (observable, callback) {
+            var arr = [];
+            observable.Subscribe(
+                function (item) { arr.push(item); },
+                function (err) { arr.push({ "__error__": err }); },
+                function () { callback(arr); });
+        };
+
+        toArray(actual, function (actualSequence) {
+            toArray(expected, function (expectedSequence) {
+                djstest.assertAreEqualDeep(actualSequence, expectedSequence, "Verify observable sequence");
+                done();
+            });
+        });
+    };
+
+    module("Functional");
+    $.each(feeds, function (_, feed) {
+        $.each(pageSizes, function (_, pageSize) {
+            $.each(operatorTests, function (_, operator) {
+                var params = { feedUri: feed.uri, pageSize: pageSize, operator: operator };
+                djstest.addTest(function (params) {
+                    djstest.assertsExpected(1);
+                    var options = { name: "cache" + new Date().valueOf(), source: params.feedUri, pageSize: params.pageSize, prefetchSize: 0 };
+                    var cache = odatajs.cache.createDataCache(options);
+
+                    ODataReadOracle.readJsonAcrossServerPages(params.feedUri, function (collection) {
+                        assertObservables(params.operator(cache.toObservable()), params.operator(window.Rx.Observable.FromArray(collection.value)), function () {
+                            djstest.destroyCacheAndDone(cache);
+                        });
+                    });
+                }, "feed: " + params.feedUri + ", pageSize: " + params.pageSize + ", operator: " + params.operator.toString(), params);
+            });
+        });
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/d5ec5557/odatajs/tests/odata-fuzz.html
----------------------------------------------------------------------
diff --git a/odatajs/tests/odata-fuzz.html b/odatajs/tests/odata-fuzz.html
new file mode 100644
index 0000000..270e153
--- /dev/null
+++ b/odatajs/tests/odata-fuzz.html
@@ -0,0 +1,557 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+ -->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>OData Fuzzing Tests</title>
+    <meta http-equiv="cache-control" content="no-cache" />
+    <meta http-equiv="pragma" content="no-cache" />
+    <meta http-equiv="expires" content="-1" />
+    <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
+    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.js"></script>
+    <script type="text/javascript" src="../build/odatajs-4.0.0-beta-01.js"></script>   
+    <script type="text/javascript" src="common/common.js"></script>
+    <script type="text/javascript">
+
+        var testCsdl = '' +
+        '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n' +
+        '<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">\r\n' +
+        '  <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0">\r\n' +
+        '    <Schema Namespace="TestCatalog.Model" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">\r\n' +
+        '      <EntityType Name="Genre">\r\n' +
+        '        <Key><PropertyRef Name="Name" /></Key>\r\n' +
+        '        <Property Name="Name" Type="Edm.String" Nullable="false" MaxLength="50" Unicode="false" FixedLength="false" m:FC_TargetPath="SyndicationTitle" />\r\n' +
+        '        <NavigationProperty Name="Titles" Relationship="TestCatalog.Model.TitleGenres" FromRole="Genres" ToRole="Titles" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <EntityType Name="Language">\r\n' +
+        '        <Key><PropertyRef Name="Name" /></Key>\r\n' +
+        '        <Property Name="Name" Type="Edm.String" Nullable="false" MaxLength="80" Unicode="false" FixedLength="false" m:FC_TargetPath="SyndicationTitle" />\r\n' +
+        '        <NavigationProperty Name="Titles" Relationship="TestCatalog.Model.TitleLanguages" FromRole="Language" ToRole="Titles" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <EntityType Name="Person">\r\n' +
+        '        <Key><PropertyRef Name="Id" /></Key>\r\n' +
+        '        <Property Name="Id" Type="Edm.Int32" Nullable="false" />\r\n' +
+        '        <Property Name="Name" Type="Edm.String" Nullable="false" MaxLength="80" Unicode="true" FixedLength="false" m:FC_TargetPath="SyndicationTitle" />\r\n' +
+        '        <NavigationProperty Name="Awards" Relationship="TestCatalog.Model.FK_TitleAward_Person" FromRole="People" ToRole="TitleAwards" />\r\n' +
+        '        <NavigationProperty Name="TitlesActedIn" Relationship="TestCatalog.Model.TitleActors" FromRole="People" ToRole="Titles" />\r\n' +
+        '        <NavigationProperty Name="TitlesDirected" Relationship="TestCatalog.Model.TitleDirectors" FromRole="People" ToRole="Titles" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <EntityType Name="TitleAudioFormat">\r\n' +
+        '        <Key><PropertyRef Name="TitleId" /><PropertyRef Name="DeliveryFormat" /><PropertyRef Name="Language" /><PropertyRef Name="Format" /></Key>\r\n' +
+        '        <Property Name="TitleId" Type="Edm.String" Nullable="false" MaxLength="30" FixedLength="false" />\r\n' +
+        '        <Property Name="DeliveryFormat" Type="Edm.String" Nullable="false" MaxLength="10" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="Language" Type="Edm.String" Nullable="false" MaxLength="30" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="Format" Type="Edm.String" Nullable="false" MaxLength="30" Unicode="false" FixedLength="false" />\r\n' +
+        '        <NavigationProperty Name="Title" Relationship="TestCatalog.Model.FK_TitleAudioFormat_Title" FromRole="TitleAudioFormats" ToRole="Titles" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <EntityType Name="TitleAward">\r\n' +
+        '        <Key><PropertyRef Name="Id" /></Key>\r\n' +
+        '        <Property Name="Id" Type="Edm.Guid" Nullable="false" />\r\n' +
+        '        <Property Name="Type" Type="Edm.String" Nullable="false" MaxLength="30" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="Category" Type="Edm.String" Nullable="false" MaxLength="60" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="Year" Type="Edm.Int32" Nullable="true" />\r\n' +
+        '        <Property Name="Won" Type="Edm.Boolean" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="Title" Relationship="TestCatalog.Model.FK_TitleAward_Title" FromRole="TitleAwards" ToRole="Titles" />\r\n' +
+        '        <NavigationProperty Name="Person" Relationship="TestCatalog.Model.FK_TitleAward_Person" FromRole="TitleAwards" ToRole="People" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <EntityType Name="Title" m:HasStream="true">\r\n' +
+        '        <Key><PropertyRef Name="Id" /></Key>\r\n' +
+        '        <Property Name="Id" Type="Edm.String" Nullable="false" MaxLength="30" FixedLength="false" />\r\n' +
+        '        <Property Name="Synopsis" Type="Edm.String" Nullable="true" MaxLength="Max" Unicode="false" FixedLength="false" m:FC_TargetPath="SyndicationSummary" m:FC_ContentKind="html" />\r\n' +
+        '        <Property Name="ShortSynopsis" Type="Edm.String" Nullable="true" MaxLength="Max" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="AverageRating" Type="Edm.Double" Nullable="true" />\r\n' +
+        '        <Property Name="ReleaseYear" Type="Edm.Int32" Nullable="true" />\r\n' +
+        '        <Property Name="Url" Type="Edm.String" Nullable="true" MaxLength="200" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="Runtime" Type="Edm.Int32" Nullable="true" />\r\n' +
+        '        <Property Name="Rating" Type="Edm.String" Nullable="true" MaxLength="10" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="DateModified" Type="Edm.DateTime" Nullable="false" ConcurrencyMode="Fixed" m:FC_TargetPath="SyndicationUpdated" m:FC_KeepInContent="false" />\r\n' +
+        '        <Property Name="Type" Type="Edm.String" Nullable="false" MaxLength="8" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="BoxArt" Type="TestCatalog.Model.BoxArt" Nullable="false" />\r\n' +
+        '        <Property Name="ShortName" Type="Edm.String" Nullable="false" MaxLength="200" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="Name" Type="Edm.String" Nullable="false" MaxLength="200" Unicode="false" FixedLength="false" m:FC_TargetPath="SyndicationTitle" />\r\n' +
+        '        <Property Name="Instant" Type="TestCatalog.Model.InstantAvailability" Nullable="false" />\r\n' +
+        '        <Property Name="Dvd" Type="TestCatalog.Model.DeliveryFormatAvailability" Nullable="false" />\r\n' +
+        '        <Property Name="BluRay" Type="TestCatalog.Model.DeliveryFormatAvailability" Nullable="false" />\r\n' +
+        '        <Property Name="TinyUrl" Type="Edm.String" Nullable="false" MaxLength="30" />\r\n' +
+        '        <Property Name="WebsiteUrl" Type="Edm.String" Nullable="true" MaxLength="200" />\r\n' +
+        '        <Property Name="TestApiId" Type="Edm.String" Nullable="false" MaxLength="200" />\r\n' +
+        '        <NavigationProperty Name="AudioFormats" Relationship="TestCatalog.Model.FK_TitleAudioFormat_Title" FromRole="Titles" ToRole="TitleAudioFormats" />\r\n' +
+        '        <NavigationProperty Name="Awards" Relationship="TestCatalog.Model.FK_TitleAward_Title" FromRole="Titles" ToRole="TitleAwards" />\r\n' +
+        '        <NavigationProperty Name="Disc" Relationship="TestCatalog.Model.FK_Title_Disc" FromRole="Titles1" ToRole="Titles" />\r\n' +
+        '        <NavigationProperty Name="Movie" Relationship="TestCatalog.Model.FK_Title_Movie" FromRole="Titles1" ToRole="Titles" />\r\n' +
+        '        <NavigationProperty Name="Season" Relationship="TestCatalog.Model.FK_Title_Season" FromRole="Titles1" ToRole="Titles" />\r\n' +
+        '        <NavigationProperty Name="Series" Relationship="TestCatalog.Model.FK_Title_Series" FromRole="Titles1" ToRole="Titles" />\r\n' +
+        '        <NavigationProperty Name="ScreenFormats" Relationship="TestCatalog.Model.FK_TitleScreenFormat_Title" FromRole="Titles" ToRole="TitleScreenFormats" />\r\n' +
+        '        <NavigationProperty Name="Cast" Relationship="TestCatalog.Model.TitleActors" FromRole="Titles" ToRole="People" />\r\n' +
+        '        <NavigationProperty Name="Languages" Relationship="TestCatalog.Model.TitleLanguages" FromRole="Titles" ToRole="Language" />\r\n' +
+        '        <NavigationProperty Name="Directors" Relationship="TestCatalog.Model.TitleDirectors" FromRole="Titles" ToRole="People" />\r\n' +
+        '        <NavigationProperty Name="Genres" Relationship="TestCatalog.Model.TitleGenres" FromRole="Titles" ToRole="Genres" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <ComplexType Name="BoxArt">\r\n' +
+        '        <Property Name="SmallUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="MediumUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="LargeUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="HighDefinitionUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="false" FixedLength="false" />\r\n' +
+        '      </ComplexType>\r\n' +
+        '      <ComplexType Name="InstantAvailability">\r\n' +
+        '        <Property Name="Available" Type="Edm.Boolean" Nullable="false" />\r\n' +
+        '        <Property Name="AvailableFrom" Type="Edm.DateTime" Nullable="true" />\r\n' +
+        '        <Property Name="AvailableTo" Type="Edm.DateTime" Nullable="true" />\r\n' +
+        '        <Property Name="HighDefinitionAvailable" Type="Edm.Boolean" Nullable="false" />\r\n' +
+        '        <Property Name="Runtime" Type="Edm.Int32" Nullable="true" />\r\n' +
+        '        <Property Name="Rating" Type="Edm.String" Nullable="true" MaxLength="10" Unicode="false" FixedLength="false" />\r\n' +
+        '      </ComplexType>\r\n' +
+        '      <ComplexType Name="DeliveryFormatAvailability">\r\n' +
+        '        <Property Name="Available" Type="Edm.Boolean" Nullable="false" />\r\n' +
+        '        <Property Name="AvailableFrom" Type="Edm.DateTime" Nullable="true" />\r\n' +
+        '        <Property Name="AvailableTo" Type="Edm.DateTime" Nullable="true" />\r\n' +
+        '        <Property Name="Runtime" Type="Edm.Int32" Nullable="true" />\r\n' +
+        '        <Property Name="Rating" Type="Edm.String" Nullable="true" MaxLength="10" Unicode="false" FixedLength="false" />\r\n' +
+        '      </ComplexType>\r\n' +
+        '      <EntityType Name="TitleScreenFormat">\r\n' +
+        '        <Key><PropertyRef Name="TitleId" /><PropertyRef Name="DeliveryFormat" /><PropertyRef Name="Format" /></Key>\r\n' +
+        '        <Property Name="TitleId" Type="Edm.String" Nullable="false" MaxLength="30" FixedLength="false" />\r\n' +
+        '        <Property Name="DeliveryFormat" Type="Edm.String" Nullable="false" MaxLength="10" Unicode="false" FixedLength="false" />\r\n' +
+        '        <Property Name="Format" Type="Edm.String" Nullable="false" MaxLength="30" Unicode="false" FixedLength="false" />\r\n' +
+        '        <NavigationProperty Name="Title" Relationship="TestCatalog.Model.FK_TitleScreenFormat_Title" FromRole="TitleScreenFormats" ToRole="Titles" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <Association Name="FK_TitleAudioFormat_Title">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="1" />\r\n' +
+        '        <End Role="TitleAudioFormats" Type="TestCatalog.Model.TitleAudioFormat" Multiplicity="*" />\r\n' +
+        '        <ReferentialConstraint>\r\n' +
+        '          <Principal Role="Titles"><PropertyRef Name="Id" /></Principal>\r\n' +
+        '          <Dependent Role="TitleAudioFormats"><PropertyRef Name="TitleId" /></Dependent>\r\n' +
+        '        </ReferentialConstraint>\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="FK_TitleAward_Title">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="1" />\r\n' +
+        '        <End Role="TitleAwards" Type="TestCatalog.Model.TitleAward" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="FK_TitleAward_Person">\r\n' +
+        '        <End Role="People" Type="TestCatalog.Model.Person" Multiplicity="0..1" />\r\n' +
+        '        <End Role="TitleAwards" Type="TestCatalog.Model.TitleAward" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="FK_Title_Disc">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="0..1" />\r\n' +
+        '        <End Role="Titles1" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="FK_Title_Movie">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="0..1" />\r\n' +
+        '        <End Role="Titles1" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="FK_Title_Season">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="0..1" />\r\n' +
+        '        <End Role="Titles1" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="FK_Title_Series">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="0..1" />\r\n' +
+        '        <End Role="Titles1" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="FK_TitleScreenFormat_Title">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="1" />\r\n' +
+        '        <End Role="TitleScreenFormats" Type="TestCatalog.Model.TitleScreenFormat" Multiplicity="*" />\r\n' +
+        '        <ReferentialConstraint>\r\n' +
+        '          <Principal Role="Titles"><PropertyRef Name="Id" /></Principal>\r\n' +
+        '          <Dependent Role="TitleScreenFormats"><PropertyRef Name="TitleId" /></Dependent>\r\n' +
+        '        </ReferentialConstraint>\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="TitleActors">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '        <End Role="People" Type="TestCatalog.Model.Person" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="TitleLanguages">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '        <End Role="Language" Type="TestCatalog.Model.Language" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="TitleDirectors">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '        <End Role="People" Type="TestCatalog.Model.Person" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '      <Association Name="TitleGenres">\r\n' +
+        '        <End Role="Titles" Type="TestCatalog.Model.Title" Multiplicity="*" />\r\n' +
+        '        <End Role="Genres" Type="TestCatalog.Model.Genre" Multiplicity="*" />\r\n' +
+        '      </Association>\r\n' +
+        '    </Schema>\r\n' +
+        '    <Schema Namespace="Test.Catalog" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">\r\n' +
+        '      <EntityContainer Name="TestCatalog" m:IsDefaultEntityContainer="true">\r\n' +
+        '        <FunctionImport Name="Movies" EntitySet="Titles" ReturnType="Collection(TestCatalog.Model.Title)" m:HttpMethod="GET" />\r\n' +
+        '        <FunctionImport Name="Series" EntitySet="Titles" ReturnType="Collection(TestCatalog.Model.Title)" m:HttpMethod="GET" />\r\n' +
+        '        <FunctionImport Name="Seasons" EntitySet="Titles" ReturnType="Collection(TestCatalog.Model.Title)" m:HttpMethod="GET" />\r\n' +
+        '        <FunctionImport Name="Discs" EntitySet="Titles" ReturnType="Collection(TestCatalog.Model.Title)" m:HttpMethod="GET" />\r\n' +
+        '        <FunctionImport Name="Episodes" EntitySet="Titles" ReturnType="Collection(TestCatalog.Model.Title)" m:HttpMethod="GET" />\r\n' +
+        '        <EntitySet Name="Genres" EntityType="TestCatalog.Model.Genre" />\r\n' +
+        '        <EntitySet Name="Languages" EntityType="TestCatalog.Model.Language" />\r\n' +
+        '        <EntitySet Name="People" EntityType="TestCatalog.Model.Person" />\r\n' +
+        '        <EntitySet Name="TitleAudioFormats" EntityType="TestCatalog.Model.TitleAudioFormat" />\r\n' +
+        '        <EntitySet Name="TitleAwards" EntityType="TestCatalog.Model.TitleAward" />\r\n' +
+        '        <EntitySet Name="Titles" EntityType="TestCatalog.Model.Title" />\r\n' +
+        '        <EntitySet Name="TitleScreenFormats" EntityType="TestCatalog.Model.TitleScreenFormat" />\r\n' +
+        '        <AssociationSet Name="FK_TitleAudioFormat_Title" Association="TestCatalog.Model.FK_TitleAudioFormat_Title">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="TitleAudioFormats" EntitySet="TitleAudioFormats" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="FK_TitleAward_Title" Association="TestCatalog.Model.FK_TitleAward_Title">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="TitleAwards" EntitySet="TitleAwards" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="FK_TitleAward_Person" Association="TestCatalog.Model.FK_TitleAward_Person">\r\n' +
+        '          <End Role="People" EntitySet="People" />\r\n' +
+        '          <End Role="TitleAwards" EntitySet="TitleAwards" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="FK_Title_Disc" Association="TestCatalog.Model.FK_Title_Disc">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="Titles1" EntitySet="Titles" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="FK_Title_Movie" Association="TestCatalog.Model.FK_Title_Movie">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="Titles1" EntitySet="Titles" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="FK_Title_Season" Association="TestCatalog.Model.FK_Title_Season">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="Titles1" EntitySet="Titles" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="FK_Title_Series" Association="TestCatalog.Model.FK_Title_Series">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="Titles1" EntitySet="Titles" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="FK_TitleScreenFormat_Title" Association="TestCatalog.Model.FK_TitleScreenFormat_Title">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="TitleScreenFormats" EntitySet="TitleScreenFormats" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="TitleActors" Association="TestCatalog.Model.TitleActors">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="People" EntitySet="People" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="TitleLanguages" Association="TestCatalog.Model.TitleLanguages">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="Language" EntitySet="Languages" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="TitleDirectors" Association="TestCatalog.Model.TitleDirectors">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="People" EntitySet="People" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '        <AssociationSet Name="TitleGenres" Association="TestCatalog.Model.TitleGenres">\r\n' +
+        '          <End Role="Titles" EntitySet="Titles" />\r\n' +
+        '          <End Role="Genres" EntitySet="Genres" />\r\n' +
+        '        </AssociationSet>\r\n' +
+        '      </EntityContainer>\r\n' +
+        '    </Schema>\r\n' +
+        '  </edmx:DataServices>\r\n' +
+        '</edmx:Edmx>\r\n' +
+        '';
+
+
+        var iterationsRemaining = 0;
+        var iterationsPerRun = 100000;
+        var failureCount = 0;
+        var canary = false;
+
+        function canaryFn() {
+            canary = true;
+        }
+
+        var buildSumAndNormalize = function (table) {
+            // Sum all weights.
+            var total = 0;
+            for (var i = 0; i < table.length; i++) {
+                total += table[i].w;
+            }
+
+            // Record a normalized weight (nw) and the step-ladder threshold.
+            var last = 0;
+            for (var i = 0; i < table.length; i++) {
+                table[i].nw = (table[i].w / total);
+
+                last += table[i].nw;
+                table[i].sl = last;
+            }
+
+            table.total = total;
+        };
+
+        var randomFromWeighted = function (table) {
+            if (table.total === undefined) {
+                buildSumAndNormalize(table);
+            }
+
+            var number = Math.random();
+            for (var i = 0; i < table.length; i++) {
+                if (number < table[i].sl) {
+                    return table[i].v;
+                }
+            }
+
+            return table[table.length - 1].v;
+        };
+
+        // Create a table with relative weight w and value v for text generation.
+        var fuzzingTextTable = [
+            { w: 3, v: "canary=true" },
+            { w: 2, v: "canary=true;" },
+            { w: 3, v: "canary=1" },
+            { w: 1, v: "window.canary=1" },
+            { w: 2, v: "canary=[]" },
+            { w: 2, v: "canary={}" },
+            { w: 1, v: "canaryFn" },
+            { w: 2, v: "canaryFn()" }
+        ];
+
+        var fuzzingTextOpTable = [
+            { w: 1, v: "replace" },
+            { w: 3, v: "insert" }
+        ];
+
+        var fuzzText = function (text) {
+            /** Fuzzes the specified string.
+             * @param {String} text - Text to fuzz.
+             * @returns {String} The fuzzes text.
+
+            var location = Math.round(Math.random() * text.length - 1);
+            var content = randomFromWeighted(fuzzingTextTable);
+            var op = randomFromWeighted(fuzzingTextOpTable);
+            if (op === "replace") {
+                text = text.substr(0, location) + content + text.substr(location + content.length);
+            } else if (op === "insert") {
+                text = text.substr(0, location) + content + text.substr(location);
+            }
+
+            return text;
+        };
+
+        var rarely = function (rare, common) {
+            if (Math.random() < 0.1) {
+                return rare;
+            } else {
+                return common;
+            }
+        };
+
+        var gibberish = function () {
+            var len = Math.random() * 100;
+            var result = "";
+            for (var i = 0; i < len; i++) {
+                result += String.fromCharCode(Math.round(Math.random() * 65000));
+            }
+
+            return result;
+        }
+
+        var generateResponse = function (suggestedContentType, body) {
+            var url = rarely(null, "http://foo/bar");
+            var statusCode = rarely((Math.random() * 1000).toString().substr(0, 3), 200);
+            var statusText = rarely(gibberish(), "OK");
+            var headers = [];
+            headers["Content-Length"] = rarely(Math.random() * 200, undefined);
+            headers["Content-Type"] = rarely("foo bar baz", suggestedContentType);
+
+            var response = { requestUri: url, statusCode: statusCode, statusText: statusText, headers: headers, body: body };
+            return response;
+        };
+
+        var generateRandom = function () {
+            var body = gibberish();
+            return generateResponse("application/octet-stream", body);
+        };
+
+        var generateServiceDoc = function() {
+            var body = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>\r\n' +
+                        '<service xml:base="http://services.odata.org/OData/OData.svc/"  xmlns:atom="http://www.w3.org/2005/Atom" ' +
+                        ' xmlns:app="http://www.w3.org/2007/app" xmlns="http://www.w3.org/2007/app">' +
+                        '<workspace><atom:title>Default</atom:title> <collection href="Products"><atom:title>Products</atom:title></collection>' +
+                        '<collection href="Categories"><atom:title>Categories</atom:title> </collection><collection href="Suppliers"><atom:title>Suppliers</atom:title> </collection></workspace></service>';
+            body = fuzzText(body);
+            return generateResponse("application/atomsvc+xml", body);
+        };
+
+        var generateMetadata = function () {
+            var body = testCsdl;
+            body = fuzzText(body);
+            return generateResponse("application/xml", body);
+        };
+
+        var generateError = function () {
+            var body = testCsdl;
+            body = fuzzText(body);
+            return generateResponse("application/xml", body);
+        };
+
+        var generatePlainText = function () {
+            var body = gibberish();
+            return generateResponse("plain/text", body);
+        };
+
+        var generateBatch = function () {
+            var body = gibberish();
+            return generateResponse("multipart/mixed; boundary=batch(123)", body);
+        };
+
+        var generateXml = function () {
+            // TODO: consider doing something about an XHTML section here
+            var body = '' +
+                '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n' +
+                '<feed xml:base=http://services.odata.org/OData/OData.svc/' +
+                '     xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices' +
+                '     xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' +
+                '     xmlns="http://www.w3.org/2005/Atom">' +
+                '  <title type="text">Categories</title>' +
+                '  <id>http://services.odata.org/OData/OData.svc/Categories</id>' +
+                '  <updated>2010-03-10T08:38:14Z</updated>' +
+                '  <link rel="self" title="Categories" href="Categories" />' +
+                '  <entry>' +
+                '    <id>http://services.odata.org/OData/OData.svc/Categories(0)</id>' +
+                '    <title type="text">Food</title>' +
+                '    <updated>2010-03-10T08:38:14Z</updated>' +
+                '    <author><name /></author>' +
+                '    <link rel="edit" title="Category" href="Categories(0)" />' +
+                '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Products"' +
+                '            type="application/atom+xml;type=feed"' +
+                '            title="Products" href="Categories(0)/Products" />' +
+                '    <category term="ODataDemo.Category"' +
+                '            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+                '    <content type="application/xml">' +
+                '      <m:properties>' +
+                '        <d:ID m:type="Edm.Int32">0</d:ID>' +
+                '        <d:Name>Food</d:Name>' +
+                '        <d:AverageRating m:type="Edm.Double">3.6</d:AverageRating>' +
+                '        <d:AvailableFrom m:type="Edm.DateTime">1999-03-23T00:00:00</d:AvailableFrom>' +
+                '        <d:Name>Food</d:Name>' +
+                '      </m:properties>' +
+                '    </content>' +
+                '  </entry>' +
+                '  <!-- <entry> elements representing additional Categories go here -->' +
+                '</feed>';
+            return generateResponse("application/atom+xml", body);
+        };
+
+        var generateJson = function () {
+            var body =  '{ "d" : { ' +
+                        '"results": [ ' +
+                        '{ ' +
+                        '"__metadata": {  ' +
+                        '"uri": "http://services.odata.org/OData/OData.svc/Categories(0)",  ' +
+                        '"type": "DataServiceProviderDemo.Category" ' +
+                        '}, ' +
+                        '"ID": 0, ' +
+                        '"Name": "Food", ' +
+                        '"SomeDate": "Date(123)", ' +
+                        '"Products": { ' +
+                        '"__deferred": { ' +
+                        '"uri": "http://services.odata.org/OData/OData.svc/Categories(0)/Products" ' +
+                        '} } } ], ' +
+                        '"__count": "3", ' +
+                        '"__next": "http://services.odata.org/OData/OData.svc$skiptoken=12" ' +
+                        '} } ' +
+                        '';
+            return generateResponse("application/json", body);
+        };
+
+        // Create a table with relative weight w and value v for response generation.
+        var formatTable = [
+            { w:  1, v: generateRandom },
+            { w:  2, v: generateError },
+            { w:  5, v: generatePlainText },
+            { w:  5, v: generateServiceDoc },
+            { w: 10, v: generateBatch },
+            { w: 10, v: generateMetadata },
+            { w: 40, v: generateXml },
+            { w: 80, v: generateJson }
+        ];
+
+        var fakeHttpClient = {
+            request: function (request, success, error) {
+                /** Performs a network request.
+                 * @param {Function} success - Success callback with the response object.
+                 * @param {Function} error - Error callback with an error object.
+                 * @returns {Object} Object with an 'abort' method for the operation.
+
+                var shouldError = rarely(true, false);
+                var format = randomFromWeighted(formatTable);
+                var response = format();
+
+                window.setTimeout(function () {
+                    if (shouldError) {
+                        error(response);
+                    } else {
+                        success(response);
+                    }
+                }, 1);
+
+                return {};
+            }
+        };
+
+        var startTimeMs = 0;
+
+        function startTest() {
+            failureCount = 0;
+            startTimeMs = new Date().getTime();
+            iterationsRemaining = iterationsPerRun;
+            nextIteration();
+        }
+
+        function readCallback() {
+            // Success or failure don't actually matter.
+            if (canary === true) {
+                canary = false;
+                failureCount++;
+            }
+
+            iterationsRemaining--;
+            nextIteration();
+        }
+
+        function nextIteration() {
+            if (iterationsRemaining === 0) {
+                $("#fuzz-status").text("Tests complete. Failures: #" + failureCount);
+                return;
+            }
+
+            if (iterationsRemaining % 50 === 0) {
+                var text = "Running tests (pending=" + iterationsRemaining + " of " + iterationsPerRun;
+                if (iterationsRemaining !== iterationsPerRun) {
+                    var currentTimeMs = new Date().getTime();
+                    var averageTestTimeMs = (iterationsPerRun - iterationsRemaining) / (currentTimeMs - startTimeMs);
+                    var remainingTimeMs = iterationsRemaining * averageTestTimeMs;
+                    text += ", ETA: " + remainingTimeMs + "ms, avg " + averageTestTimeMs + "ms";
+                }
+
+                text += "). Failures: #" + failureCount;
+                $("#fuzz-status").text(text);
+            }
+
+            OData.read("url", readCallback, readCallback, undefined, fakeHttpClient);
+        }
+
+        $(document).ready(function () {
+            $("#start-button").click(startTest);
+        });
+    </script>
+
+</head>
+<body>
+<h1>OData Fuzzing Tests</h1>
+<p>
+This page fuzzes the OData parsers in the datajs library.
+</p>
+<button id='start-button'>Start</button>
+<p id='fuzz-status'>&nbsp;</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/d5ec5557/odatajs/tests/odata-handler-tests.js
----------------------------------------------------------------------
diff --git a/odatajs/tests/odata-handler-tests.js b/odatajs/tests/odata-handler-tests.js
new file mode 100644
index 0000000..aa824c7
--- /dev/null
+++ b/odatajs/tests/odata-handler-tests.js
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+ 
+// odata-handler-tests.js
+
+(function (window, undefined) {
+
+    var dataServiceVersion = "4.0";
+    var endpoint = "./endpoints/FoodStoreDataServiceV4.svc";
+    var mimeType = "application/json;odata.metadata=minimal";
+    var headers = {
+                "Content-Type": mimeType,
+                Accept: mimeType,
+                "OData-Version": "4.0"
+            };
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    djstest.addTest(function createUpdateDeleteTest() {
+        // This is a simple create-update-delete cycle as a high-level test.
+
+        var serviceUri = "./endpoints/FoodStoreDataServiceV4.svc/";
+        var baseUri = serviceUri + "Categories";
+        var testItem;
+        var handledData = { CategoryID: 1001, Name: "Name #1001" };
+        var uri;
+
+        var itemCreatedCallback = function (data, response) {
+            djstest.assert(response.headers["Location"], "location URL in the headers");
+            djstest.assert(data.Name, "Name #1001");
+            
+            uri = response.headers["Location"];
+            testItem = handledData;
+            testItem.Name = "Updated name";
+            
+            odatajs.oData.request({
+                method: "PUT",
+                data: testItem,
+                requestUri: uri,
+            }, itemUpdatedCallback);
+        };
+
+        var itemUpdatedCallback = function (data, response) {
+            djstest.assertAreEqual(response.statusCode, 204, "Expecting no content on update");
+            odatajs.oData.request({
+                method: "DELETE",
+                requestUri: uri
+            }, itemDeletedCallback);
+        };
+
+        var itemDeletedCallback = function (data, response) {
+            djstest.done();
+        };
+
+        $.post(serviceUri + "ResetData", function () {
+            odatajs.oData.request({
+                requestUri: baseUri,
+                method: "POST",
+                data: { CategoryID: 1001, Name: "Name #1001" }
+            }, itemCreatedCallback);
+        });
+    });
+
+    djstest.addTest(function errorHandlerTest() {
+        djstest.assertsExpected(1);
+        odatajs.oData.read("./endpoints/FoodStoreDataServiceV4.svc/Categories?$reserved-misused=true",
+            function (data) {
+                djstest.fail("expected an error callback");
+                djstest.done();
+            },
+            function (err) {
+                djstest.assert(err.response.body, "err.response.body is assigned");
+                djstest.done();
+            });
+    });
+
+    djstest.addTest(function textHandlerParseTest() {
+        djstest.assertsExpected(1);
+        MockHttpClient.clear().addResponse("textHandlerParseTest", {
+            statusCode: 200,
+            body: " text ",
+            headers: { "Content-Type": "text/plain" }
+        });
+        odatajs.oData.read("textHandlerParseTest", function (data) {
+            djstest.assertAreEqual(data, " text ", "data matches");
+            djstest.done();
+        }, function (err) {
+            djstest.fail("expected success");
+            djstest.done();
+        }, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function textHandlerParseEmptyTest() {
+        djstest.assertsExpected(1);
+        MockHttpClient.clear().addResponse("textHandlerParseTest", {
+            statusCode: 200,
+            body: "",
+            headers: { "Content-Type": "text/plain" }
+        });
+        odatajs.oData.read("textHandlerParseTest", function (data) {
+            djstest.assertAreEqual(data, "", "data matches");
+            djstest.done();
+        }, function (err) {
+            djstest.fail("expected success");
+            djstest.done();
+        }, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function textHandlerSerializeTest() {
+        djstest.assertsExpected(1);
+        MockHttpClient.clear().addRequestVerifier("uri", function (request) {
+            djstest.assertAreEqual(request.body, "text", "text in request");
+        }).addResponse("uri", { statusCode: 200, body: "", headers: { "Content-Type": "text/plain"} });
+        odatajs.oData.request({ requestUri: "uri", method: "POST", data: "text", headers: { "Content-Type": "text/plain"} }, function (data) {
+            djstest.done();
+        }, function (err) {
+            djstest.fail("expected success");
+            djstest.done();
+        }, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function textHandlerSerializeBlankTest() {
+        djstest.assertsExpected(1);
+        MockHttpClient.clear().addRequestVerifier("uri", function (request) {
+            djstest.assertAreEqual(request.body, "", "text in request");
+        }).addResponse("uri", { statusCode: 200, body: "", headers: { "Content-Type": "text/plain"} });
+        odatajs.oData.request({ requestUri: "uri", method: "POST", data: "", headers: { "Content-Type": "text/plain"} }, function (data) {
+            djstest.done();
+        }, function (err) {
+            djstest.fail("expected success");
+            djstest.done();
+        }, undefined, MockHttpClient);
+    });
+
+    // DATAJS INTERNAL START
+    djstest.addTest(function handlerReadTest() {
+        var tests = [
+            {
+                response: { headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, body: "response 0" },
+                shouldHit: true,
+                context: { contentType: window.odatajs.oData.handler.contentType("application/json"), dataServiceVersion: "4.0" }
+            },
+            {
+                response: { headers: { "Content-Type": "application/json" }, body: "response 1" },
+                shouldHit: true,
+                context: { contentType: window.odatajs.oData.handler.contentType("application/json"), dataServiceVersion: "" }
+            },
+            {
+                response: { headers: { "Content-Type": "otherMediaType" }, body: "response 2" },
+                shouldHit: false,
+                context: { contentType: window.odatajs.oData.handler.contentType("otherMediaType"), dataServiceVersion: "" }
+            },
+            {
+                response: { headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, body: "response 3" },
+                shouldHit: true,
+                context: { contentType: window.odatajs.oData.handler.contentType("application/json"), dataServiceVersion: "4.0" }
+            },
+            {
+                response: { body: "response 4" },
+                shouldHit: false,
+                context: { contentType: window.odatajs.oData.handler.contentType("application/json"), dataServiceVersion: "" }
+            },
+            {
+                response: null,
+                shouldHit: false,
+                context: {}
+            },
+            {
+                response: undefined,
+                shouldHit: false,
+                context: {}
+            }
+        ];
+
+        var i;
+        var test;
+        var testRead = function (handler, body, context) {
+            djstest.assert(test.shouldHit, "method should be hit on item #" + i);
+            djstest.assertAreEqual(handler, testHandler, "handler matches target on item #" + i);
+            djstest.assertAreEqualDeep(context, test.context, "context matches target on item #" + i);
+            djstest.assertAreEqual(body, test.response.body, "body matches target on item #" + i);
+            return body;
+        };
+
+        var testHandler = window.odatajs.oData.handler.handler(testRead, null, "application/json", "4.0");
+
+        var len, expectedAssertCount = 0;
+        for (i = 0, len = tests.length; i < len; i++) {
+            test = tests[i];
+            test.context.handler = testHandler;
+            test.context.response = test.response;
+            if (test.shouldHit) {
+                expectedAssertCount += 4;
+            }
+
+            testHandler.read(test.response, {});
+        }
+
+        djstest.assertsExpected(expectedAssertCount);
+        djstest.done();
+    });
+
+    djstest.addTest(function handlerWriteTest() {
+        var tests = [
+            {
+                request: { headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, data: "request 0" },
+                shouldHit: true,
+                context: { contentType: window.odatajs.oData.handler.contentType("application/json"), dataServiceVersion: "4.0" }
+            },
+            {
+                request: { headers: { "Content-Type": "application/json" }, data: "request 1" },
+                shouldHit: true,
+                context: { contentType: window.odatajs.oData.handler.contentType("application/json"), dataServiceVersion: undefined }
+            },
+            {
+                request: { headers: { "Content-Type": "otherMediaType" }, data: "request 2" },
+                shouldHit: false,
+                context: { contentType: window.odatajs.oData.handler.contentType("otherMediaType"), dataServiceVersion: undefined }
+            },
+            {
+                request: { headers: {}, data: "request 3" },
+                shouldHit: true,
+                context: { contentType: null, dataServiceVersion: undefined }
+            },
+            {
+                request: { headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, data: "request 4" },
+                shouldHit: true,
+                context: { contentType: window.odatajs.oData.handler.contentType("application/json"), dataServiceVersion: "4.0" }
+            },
+            {
+                request: null,
+                shouldHit: false,
+                context: {}
+            },
+            {
+                request: undefined,
+                shouldHit: false,
+                context: {}
+            }
+        ];
+
+        var test;
+        var testWrite = function (handler, data, context) {
+            djstest.assert(test.shouldHit, "method should be hit");
+            djstest.assertAreEqual(handler, testHandler, "handler matches target");
+            djstest.assertAreEqualDeep(context, test.context, "context matches target");
+            djstest.assertAreEqual(data, test.request.data, "body matches target");
+            return data;
+        };
+
+        var testHandler = window.odatajs.oData.handler.handler(null, testWrite, "application/json", "4.0");
+
+        var i, len, expectedAssertCount = 0;
+        for (i = 0, len = tests.length; i < len; i++) {
+            test = tests[i];
+            test.context.handler = testHandler;
+            test.context.request = test.request;
+            if (test.shouldHit) {
+                expectedAssertCount += 4;
+            }
+            testHandler.write(test.request);
+        }
+
+        djstest.assertsExpected(expectedAssertCount);
+        djstest.done();
+    });
+
+    djstest.addTest(function handlerWriteUpdatesRequestContentTypeTest() {
+        var testWrite = function (handler, data, context) {
+            context.contentType = window.odatajs.oData.handler.contentType("my new content type");
+            return data;
+        };
+
+        var testHandler = window.odatajs.oData.handler.handler(null, testWrite, "application/json", "4.0");
+
+        var tests = [
+            { request: { headers: { "Content-Type": "application/json" }, data: "request 0" }, expected: "application/json" },
+            { request: { headers: {}, data: "request 1" }, expected: "my new content type" }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            testHandler.write(tests[i].request);
+            djstest.assertAreEqual(tests[i].request.headers["Content-Type"], tests[i].expected, "request content type is the expected");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function contentTypeTest() {
+        var tests = [
+            { contentType: "application/json;param1=value1;param2=value2", expected: { mediaType: "application/json", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/json; param1=value1; param2=value2", expected: { mediaType: "application/json", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/json;param1=value1; param2=value2", expected: { mediaType: "application/json", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/json; param1=value1;param2=value2", expected: { mediaType: "application/json", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/json", expected: { mediaType: "application/json", properties: {}} },
+            { contentType: ";param1=value1;param2=value2", expected: { mediaType: "", properties: { param1: "value1", param2: "value2"}} }
+        ];
+
+        var i, len, cTypeString;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var actual = window.odatajs.oData.handler.contentType(tests[i].contentType);
+            djstest.assertAreEqual(actual.mediaType, tests[i].expected.mediaType, "Content type media type is parsed correctly");
+            djstest.assertAreEqualDeep(actual.properties, tests[i].expected.properties, "Content type properties are parsed correctly");
+        }
+
+        djstest.assert(! window.odatajs.oData.handler.contentType(undefined), "contentType returns undefined for undefined input");
+        djstest.assert(! window.odatajs.oData.handler.contentType(null), "contentType returns undefined for null input");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function contentTypeToStringTest() {
+        var tests = [
+            { contentType: { mediaType: "application/json", properties: { param1: "value1", param2: "value2"} }, expected: "application/json;param1=value1;param2=value2" },
+            { contentType: { mediaType: "application/json", properties: {} }, expected: "application/json" },
+            { contentType: { mediaType: "", properties: { param1: "value1", param2: "value2"} }, expected: ";param1=value1;param2=value2" }
+        ];
+
+        var i, len, cTypeString;
+        for (i = 0, len = tests.length; i < len; i++) {
+            cTypeString = window.odatajs.oData.handler.contentTypeToString(tests[i].contentType);
+            djstest.assertAreEqual(cTypeString, tests[i].expected, "contentTypeToString returns the correct contentType string");
+        }
+
+        djstest.done();
+    });
+    
+    djstest.addTest(function readServiceDocumentTest(headers) {
+        odatajs.oData.request({
+            requestUri: endpoint,
+            method: "GET",
+            headers: headers
+        }, function (data, response) {
+            djstest.assertAreEqual(data.value[0].name, "Categories", "Verify .name");
+            djstest.assertAreEqual(data.value[0].kind, "EntitySet", "Verify .kind");
+            djstest.assertAreEqual(data.value[0].url, "Categories", "Verify .url");
+
+            djstest.assertAreEqual(data.value[1].name, "Foods", "Verify .name");
+            djstest.assertAreEqual(data.value[1].kind, "EntitySet", "Verify .kind");
+            djstest.assertAreEqual(data.value[1].url, "Foods", "Verify .url");
+            djstest.done();
+        }, unexpectedErrorHandler);
+     });
+          
+    // DATAJS INTERNAL END
+})(this);
\ No newline at end of file