You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by bi...@apache.org on 2014/05/15 03:09:35 UTC

[01/10] [OLINGO-276] Check in the missing test source codes.

Repository: olingo-odata4-js
Updated Branches:
  refs/heads/master 1b51dcbec -> 48761e07f


http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-xml-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-xml-tests.js b/JSLib/tests/odata-xml-tests.js
new file mode 100644
index 0000000..64605e8
--- /dev/null
+++ b/JSLib/tests/odata-xml-tests.js
@@ -0,0 +1,259 @@
+/// <reference path="../src/odata-xml.js" />
+/// <reference path="common/djstest.js" />
+
+// odata-xml-tests.js
+
+(function (window, undefined) {
+
+    // DATAJS INTERNAL START
+
+    djstest.addTest(function getURIInfoTest() {
+        var tests = [
+            { input: "https://host.com:8080/path1/path2?p1=1&p2=2#fragment", expected: { scheme: "https:", authority: "//host.com:8080", path: "/path1/path2", query: "?p1=1&p2=2", fragment: "#fragment", isAbsolute: true} },
+            { input: "http://host.com:8080/path1/path2?p1=1&p2=2#fragment", expected: { scheme: "http:", authority: "//host.com:8080", path: "/path1/path2", query: "?p1=1&p2=2", fragment: "#fragment", isAbsolute: true} },
+            { input: "https:", expected: { scheme: "https:", isAbsolute: true} },
+            { input: "http:", expected: { scheme: "http:", isAbsolute: true} },
+            { input: "//host.com", expected: { authority: "//host.com", isAbsolute: false} },
+            { input: "path1", expected: { path: "path1", isAbsolute: false} },
+            { input: "?query", expected: { query: "?query", isAbsolute: false} },
+            { input: "#fragment", expected: { fragment: "#fragment", isAbsolute: false} },
+            { input: undefined, expected: { isAbsolute: false} },
+            { input: "", expected: { isAbsolute: false} },
+            { input: null, expected: { isAbsolute: false} }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var actual = datajs.getURIInfo(tests[i].input);
+            djstest.assertAreEqualDeep(actual, tests[i].expected, "test " + i + "didn't return the expected URI parts");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function normalizeURICaseTest() {
+        var tests = [
+            { uri: "hTTp://HOST.com/path1/Path2/PATH3?Query1=x&query2=Y#Fragment", expected: "http://host.com/path1/Path2/PATH3?Query1=x&query2=Y#Fragment" },
+            { uri: "http://fabrikam%20user%3AHisPassWord@www.FaBriKAM.com:5895/Path%3A%201?q1=hi%20%3Ato%20you", expected: "http://fabrikam%20user%3aHisPassWord@www.fabrikam.com:5895/Path%3a%201?q1=hi%20%3ato%20you" },
+            { uri: "/PATH1/PATH2?P1=AbC#fraGment", expected: "/PATH1/PATH2?P1=AbC#fraGment" },
+            { uri: "HttP://" + encodeURIComponent("FTP://www.example.com&story=breaking_news:password@www.HOST.CoM:5678/"), expected: "http://" + encodeURIComponent("FTP://www.example.com&story=breaking_news:password@www.HOST.CoM:5678/").toLowerCase() }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var actual = datajs.normalizeURICase(tests[i].uri, tests[i].base);
+            djstest.assertAreEqual(actual, tests[i].expected, "test " + i + "didn't return the expected URI");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function normalizeURITest() {
+        var tests = [
+            { uri: "http://host.com/path1#fragment", base: "http://base", expected: "http://host.com/path1#fragment" },
+            { uri: "//host.com/path1?p1=0", base: "http://base?p2=1", expected: "http://host.com/path1?p1=0" },
+            { uri: "?p1=0#fragment", base: "http://base/basepath", expected: "http://base/basepath?p1=0#fragment" },
+            { uri: "?p1=0#fragment", base: "http://base/basepath?p2=1", expected: "http://base/basepath?p1=0#fragment" },
+            { uri: "#fragment", base: "http://base/basepath?p2=1", expected: "http://base/basepath?p2=1#fragment" },
+            { uri: "/path1/path2?p1=0", base: "http://base/basePath", expected: "http://base/path1/path2?p1=0" },
+            { uri: "path1/path2?p1=0", base: "http://base/basepath", expected: "http://base/path1/path2?p1=0" },
+            { uri: "path1/path2?p1=0", base: "http://base/basepath/basepath2", expected: "http://base/basepath/path1/path2?p1=0" },
+            { uri: "", base: "http://base/basepath?p1=0#fragment", expected: "http://base/basepath?p1=0" },
+            { uri: "path1/path2?p1=0", base: "", expected: "path1/path2?p1=0" },
+            { uri: "/a/b/c/./../../g", base: "http://base/basepath", expected: "http://base/a/g" },
+            { uri: "a/b/c/././../../g", base: "http://base/basepath/", expected: "http://base/basepath/a/g" },
+            { uri: "../a/b/c/././../../g", base: "http://base/basepath/", expected: "http://base/a/g" },
+            { uri: "./a/b/c/././../../g", base: "http://base/basepath/", expected: "http://base/basepath/a/g" },
+            { uri: "/../a/b/c/././../../g", base: "http://base/basepath/", expected: "http://base/a/g" },
+            { uri: "/./a/b/c/././../../g", base: "http://base/basepath/", expected: "http://base/a/g" }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var actual = datajs.normalizeURI(tests[i].uri, tests[i].base);
+            djstest.assertAreEqual(actual, tests[i].expected, "test " + i + "didn't return the expected normalized URI");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlParseTest() {
+        var xml = '<root xmlns:n1="http://namespace1" xml:base="http://base.org" />';
+        var root = datajs.xmlParse(xml);
+        djstest.assert(root, "xml._parse didn't return a xml dom object");
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlbaseURITest() {
+        var xml = "\
+         <root xmlns:n1=\"http://namespace1\" \r\n\
+               xml:base=\"http://base.org\"> \r\n\
+           <element base=\"this is not a xml base attribute\" /> \r\n\
+         </root>\r\n";
+
+        var doc = datajs.xmlParse(xml);
+        var root = datajs.xmlFirstChildElement(doc);
+        var child = datajs.xmlFirstChildElement(root);
+
+        djstest.assertAreEqual(datajs.xmlBaseURI(root), "http://base.org", "xml._baseURI didn't return the expected value");
+        djstest.assert(!datajs.xmlBaseURI(child), "xml._baseURI returned a value when it wasn't expected");
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlAttributeValueTest() {
+        var xml = "\
+     <root xmlns:n1=\"http://namespace1\" \r\n\
+           xml:base=\"http://base.org\"> \r\n\
+        <element attribute=\"value\" n1:nsAttribute=\"nsValue\" /> \r\n\
+     </root> \r\n";
+
+        var doc = datajs.xmlParse(xml);
+        var root = datajs.xmlFirstChildElement(doc);
+        var child = datajs.xmlFirstChildElement(root);
+
+        djstest.assertAreEqual(datajs.xmlAttributeValue(child, "attribute"), "value", "xml._attribute didn't return the expected value for attribute");
+        djstest.assertAreEqual(datajs.xmlAttributeValue(child, "nsAttribute", "http://namespace1"), "nsValue", "xml._attribute didn't return the expected value for nsAttribute");
+        djstest.assert(!datajs.xmlAttributeValue(child, "nsAttribute"), "xml._attribute returned a value for nsAttribute without specifying a namespace");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlLocalNameTest() {
+        var xml = "<root xmlns:n1=\"http://namespace1\" /> \r\n";
+
+        var doc = datajs.xmlParse(xml);
+        var root = datajs.xmlFirstChildElement(doc);
+
+        djstest.assertAreEqual(datajs.xmlLocalName(root), "root", "xml._localName didn't return the expected localName of the root element");
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlFirstChildElement() {
+        var xml = "\
+         <root xmlns:n1=\"http://namespace1\" \r\n\
+               xml:base=\"http://base.org\"> \r\n\
+           <element1 /> \r\n\
+           <element2 /> \r\n\
+         </root>\r\n";
+
+
+        var doc = datajs.xmlParse(xml);
+        var root = datajs.xmlFirstChildElement(doc);
+        var child = datajs.xmlFirstChildElement(root);
+
+        djstest.assertAreEqual(datajs.xmlLocalName(child), "element1", "xml.firstElement returned didn't return the expected element");
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlChildElementsTest() {
+        var xml = "\
+         <root xmlns:n1=\"http://namespace1\" \r\n\
+               xml:base=\"http://base.org\"> \r\n\
+           <element1 /> \r\n\
+           <element2 xml:base=\"http://otherBase.org\" /> \r\n\
+           <n1:element3 xml:base=\"path1/path2\" /> \r\n\
+         </root>\r\n";
+
+        var expected = [
+            { localName: "element1", nsURI: null },
+            { localName: "element2", nsURI: null },
+            { localName: "element3", nsURI: "http://namespace1" }
+        ];
+
+        var actual = [];
+
+        var doc = datajs.xmlParse(xml);
+        var root = datajs.xmlFirstChildElement(doc);
+    
+        datajs.xmlChildElements(root, function (child) {
+            djstest.log("in child elements callback");
+            actual.push({
+                localName: datajs.xmlLocalName(child),
+                nsURI: datajs.xmlNamespaceURI(child)
+            });
+        });
+
+        djstest.assertAreEqualDeep(actual, expected, "xml.childElements didn't return the expected elements");
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlAttributesTest() {
+        var xml = "\
+         <root xmlns:n1=\"http://namespace1\" \r\n\
+               xml:base=\"http://base.org\" \r\n\
+               attribute=\"value\" \r\n\
+               n1:nsAttribute=\"nsValue\" />\r\n";
+
+        var expected = {
+            n1: { localName: "n1", nsURI: "http://www.w3.org/2000/xmlns/", value: "http://namespace1" },
+            base: { localName: "base", nsURI: "http://www.w3.org/XML/1998/namespace", value: "http://base.org" },
+            attribute: { localName: "attribute", nsURI: null, value: "value" },
+            nsAttribute: { localName: "nsAttribute", nsURI: "http://namespace1", value: "nsValue" }
+        };
+
+        var actual = {};
+
+        var doc = datajs.xmlParse(xml);
+        var root = datajs.xmlFirstChildElement(doc);
+
+        datajs.xmlAttributes(root, function (attribute) {
+            djstest.log("in child elements callback");
+            var localName = datajs.xmlLocalName(attribute);
+            actual[localName] = {
+                localName: localName, 
+                nsURI: datajs.xmlNamespaceURI(attribute),
+                value: attribute.value
+            };
+        });
+
+        djstest.assertAreEqualDeep(actual, expected, "xml.attributes returned didn't return the expected attributes");
+        djstest.done();
+    });
+
+    djstest.addTest(function hasLeadingOrTrailingWhitespaceTest() {
+        // tests are in text / expected format.
+        var tests = [
+            { t: "", r: false },
+            { t: " ", r: true },
+            { t: "text", r: false },
+            { t: "text with spaces", r: false },
+            { t: "not \r\n really", r: false },
+            { t: " at start", r: true },
+            { t: "at end ", r: true },
+            { t: "end\r", r: true },
+            { t: "end\n", r: true },
+            { t: "end\r\n", r: true }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var result = datajs.hasLeadingOrTrailingWhitespace(tests[i].t);
+            djstest.assertAreEqual(result, tests[i].r, "match for " + tests[i].t);
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function xmlInnerTextTest() {
+        // Tests are in test / expected format.
+        var tests = [
+            { t: "<t>text</t>", r: "text" },
+            { t: "<t>text with a <![CDATA[cdata block]]></t>", r: "text with a cdata block" },
+            { t: "<t> text </t>", r: " text " },
+            { t: "<t> </t>", r: null },
+            { t: "<t> <b>text</b> </t>", r: null },
+            { t: "<t> preceding</t>", r: " preceding" },
+            { t: "<t xml:space='preserve'> <b>text</b> </t>", r: "  " },
+            { t: "<t xml:space='default'> <b>text</b> </t>", r: null}
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var test = tests[i];
+            var doc = datajs.xmlParse(test.t);
+            var actual = datajs.xmlInnerText(doc);
+            djstest.assertAreEqual(actual, test.r, "test for [" + test.t + "]");
+        }
+
+        djstest.done();
+    });
+
+    // DATAJS INTERNAL END
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/store-indexeddb-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/store-indexeddb-tests.js b/JSLib/tests/store-indexeddb-tests.js
new file mode 100644
index 0000000..76fbb30
--- /dev/null
+++ b/JSLib/tests/store-indexeddb-tests.js
@@ -0,0 +1,246 @@
+/// <reference path="../src/local-indexeddb.js" />
+/// <reference path="common/djstest.js" />
+
+// store-indexeddb-tests.js
+
+(function (window, undefined) {
+    // DATAJS INTERNAL START
+    var unexpectedSuccess = function (key, value) {
+        djstest.fail("Unexpected call to success handler: key = " + key + ", value = " + value);
+        djstest.done();
+    };
+
+    var unexpectedError = function (e) {
+        djstest.fail("Unexpected call to error handler: " + djstest.toString(e));
+        djstest.done();
+    };
+
+    var storeCounter = 0;
+    var storeName = "test";
+
+    var getNextStoreName = function () {
+        storeCounter++;
+        return getCurrentStoreName();
+    };
+
+    var getCurrentStoreName = function(){
+        return storeName + storeCounter;
+    };
+
+    var oldWindowOnError;
+
+    if (djstest.indexedDB) {
+        module("Unit", {
+            setup: function () {
+                djstest.wait(function (done) {
+                    djstest.cleanStoreOnIndexedDb([{ name: getNextStoreName() }], done);
+                });
+
+                // FireFox 7.0.1 bubbles an error event when there is an IndexedDB error, even when the error has been handled graciously.
+                // This is a work around to keep QUnit from reporting false failures in IndexedDB negative tests.
+                oldWindowOnError = window.onerror;
+                window.onerror = null;
+            },
+            teardown: function () {
+                var store = this.store;
+                if (store) {
+                    store.close();
+                }
+
+                djstest.wait(function (done) {
+                    djstest.cleanStoreOnIndexedDb([store], done);
+                });
+
+
+                // Restore QUnit's onerror handler.
+                window.onerror = oldWindowOnError;
+            }
+        });
+
+        djstest.addTest(function testIndexedDBStoreConstructor() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            djstest.assertAreEqual(store.name, getCurrentStoreName());
+            djstest.assertAreEqual(store.mechanism, "indexeddb");
+            djstest.done();
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddGet() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key", "value", function (key, value) {
+                djstest.assertAreEqual(key, "key");
+                djstest.assertAreEqual(value, "value");
+                store.read("key", function (key, value) {
+                    djstest.assertAreEqual(key, "key");
+                    djstest.assertAreEqual(value, "value");
+                    djstest.done();
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddUpdateGet() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key", "value", function (key, value) {
+                store.update("key", "value2", function (key, value) {
+                    djstest.assertAreEqual(key, "key");
+                    djstest.assertAreEqual(value, "value2");
+                    store.read("key", function (key, value) {
+                        djstest.assertAreEqual(key, "key");
+                        djstest.assertAreEqual(value, "value2");
+                        djstest.done();
+                    }, unexpectedError);
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddOrUpdateGet() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.addOrUpdate("key", "value", function (key, value) {
+                djstest.assertAreEqual(key, "key");
+                djstest.assertAreEqual(value, "value");
+                store.addOrUpdate("key", "value2", function (key, value) {
+                    djstest.assertAreEqual(key, "key");
+                    djstest.assertAreEqual(value, "value2");
+                    store.read("key", function (key, value) {
+                        djstest.assertAreEqual(key, "key");
+                        djstest.assertAreEqual(value, "value2");
+                        djstest.done();
+                    }, unexpectedError);
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddRemoveContains() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key", "value", function (key, value) {
+                store.contains("key", function (result) {
+                    djstest.assert(result);
+                    store.remove("key", function () {
+                        djstest.pass("key removed");
+                        store.contains("key", function (result) {
+                            djstest.assert(!result);
+                            djstest.done();
+                        }, unexpectedError);
+                    }, unexpectedError);
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddConsecutiveGetAllKeys() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key", "value", function (key, value) {
+                store.add("key2", "value2", function (key, value) {
+                    store.add("key3", "value3", function (key, value) {
+                        store.getAllKeys(function (keys) {
+                            djstest.assertAreEqualDeep(keys, ["key", "key2", "key3"]);
+                            djstest.done();
+                        }, unexpectedError);
+                    }, unexpectedError);
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddArrayClear() {
+            var addedKeys = ["key", "key2", "key3"];
+            var addedValues = ["value", "value2", "value3"];
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add(addedKeys, addedValues, function (keys, values) {
+                djstest.assertAreEqualDeep(keys, addedKeys);
+                djstest.assertAreEqualDeep(values, addedValues);
+                store.clear(function () {
+                    store.getAllKeys(function (keys) {
+                        djstest.assertAreEqualDeep(keys, []);
+                        djstest.done();
+                    }, unexpectedError);
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddArrayUpdateArrayGetArray() {
+            var addedKeys = ["key", "key2", "key3"];
+            var addedValues = ["value", "value2", "value3"];
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add(addedKeys, addedValues, function (keys, values) {
+                djstest.assertAreEqualDeep(keys, addedKeys);
+                djstest.assertAreEqualDeep(values, addedValues);
+                var updatedKeys = ["key", "key3"];
+                var updatedValues = ["newValue", "newValue3"];
+                store.update(updatedKeys, updatedValues, function (keys, values) {
+                    djstest.assertAreEqualDeep(keys, updatedKeys);
+                    djstest.assertAreEqualDeep(values, updatedValues);
+                    store.read(addedKeys, function (keys, values) {
+                        djstest.assertAreEqualDeep(keys, ["key", "key2", "key3"]);
+                        djstest.assertAreEqualDeep(values, ["newValue", "value2", "newValue3"]);
+                        djstest.done();
+                    }, unexpectedError);
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddOrUpdateArrayGetArray() {
+            var expectedKeys = ["key", "key2", "key3"];
+            var expectedValues = ["value", "value2", "value3"];
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key2", "value", function (key, value) {
+                store.addOrUpdate(expectedKeys, expectedValues, function (keys, values) {
+                    djstest.assertAreEqualDeep(keys, expectedKeys);
+                    djstest.assertAreEqualDeep(values, expectedValues);
+                    store.read(keys, function (keys, values) {
+                        djstest.assertAreEqualDeep(values, expectedValues);
+                        djstest.done();
+                    }, unexpectedError);
+                }, unexpectedError);
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddDuplicate() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key", "value", function (key, value) {
+                store.add("key", "value2", unexpectedSuccess, function (err) {
+                    djstest.pass("Error callback called as expected");
+                    djstest.done();
+                });
+            }, unexpectedError);
+        });
+
+        djstest.addTest(function testIndexedDBStoreAddArrayDuplicate() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add(["key", "key2", "key"], ["value", "value2", "value3"], unexpectedSuccess, function (err) {
+                djstest.pass("Error callback called as expected");
+                djstest.done();
+            });
+        });
+
+        djstest.addTest(function testIndexedDBStoreGetArrayNonExistent() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key", "value", function (key, value) {
+                store.read(["key", "badkey"], function (keys, values) {
+                    djstest.assertAreEqualDeep(keys, ["key", "badkey"]);
+                    djstest.assertAreEqualDeep(values, ["value", undefined]);
+                    djstest.done();
+                }, unexpectedError);
+            });
+        });
+
+        djstest.addTest(function testIndexedDBStoreUpdateNonExistent() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.update("badkey", "badvalue", unexpectedSuccess, function (err) {
+                djstest.pass("Error callback called as expected");
+                djstest.done();
+            });
+        });
+
+        djstest.addTest(function testIndexedDBStoreUpdateArrayNonExistent() {
+            var store = this.store = window.datajs.IndexedDBStore.create(getCurrentStoreName());
+            store.add("key", "value", function (key, value) {
+                store.update(["key", "badkey"], ["value", "badvalue"], unexpectedSuccess, function (err) {
+                    djstest.pass("Error callback called as expected");
+                    store.read("key", function (key, value) {
+                        djstest.assertAreEqual(value, "value", "value was not changed");
+                        djstest.done();
+                    }), unexpectedError;
+                });
+            }, unexpectedError);
+        });
+    }
+    // DATAJS INTERNAL END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/store-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/store-tests.js b/JSLib/tests/store-tests.js
new file mode 100644
index 0000000..1a05849
--- /dev/null
+++ b/JSLib/tests/store-tests.js
@@ -0,0 +1,684 @@
+/// <reference path="../src/datajs-store.js">
+/// <reference path="../src/datajs-store-dom.js">
+/// <reference path="common/djstest.js" />
+
+// odata-tests.js
+(function (window, undefined) {
+
+    var cleanDomStorage = function () {
+        /// <summary>Cleans all the data saved in the browser's DOM Storage.</summary>
+        if (window.localStorage) {
+            window.localStorage.clear();
+        }
+    };
+
+    var cleanMemoryStorage = function () {
+        /// <summary>Clean memory storage is a no op.</summary>
+    };
+
+    var cleanIndexedDbStorage = function () {
+        var stores = this.stores;
+        $.each(stores, function (_, store) {
+            store.close();
+        });
+
+        djstest.wait(function (done) {
+            djstest.cleanStoreOnIndexedDb(stores, done);
+        });
+    };
+
+    var canCreateMemoryStore = function () {
+        /// <summary>Checks whether memory storage is supported by the browser.</summary>
+        /// <returns type="Boolean">True.</summary>
+        return true;
+    };
+
+    var canCreateDomStore = function () {
+        /// <summary>Checks whether Web Storage (DOM Storage) is supported by the browser.</summary>
+        /// <returns type="Boolean">True if DOM Storage is supported by the browser; false otherwise.</summary>
+        return !!window.localStorage;
+    };
+
+    var canCreateIndexedDb = function () {
+        /// <summary>Checks whether Web Storage (DOM Storage) is supported by the browser.</summary>
+        /// <returns type="Boolean">True if IndexedDB is supported by the browser, false otherwise.</returns>
+        return !!djstest.indexedDB;
+    };
+
+    var canCreateStore = function (mechanism) {
+        /// <summary>Determines whether a particular mechanism is supported by the browser.</summary>
+        /// <param name="mechanism" type="String">Mechanism name.</param>
+        /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</summary>
+        var implementation = mechanismImplementations[mechanism];
+        return implementation && implementation.canCreate();
+    }
+    var makeUnexpectedErrorHandler = function (fail) {
+        return function (err) {
+            djstest.fail("error: " + err.name + " -- message: " + err.message);
+            fail();
+        };
+    };
+
+    var testJobDone = function (succeeded) {
+        if (!succeeded) {
+            djstest.fail("Job completed but some of the functions it called failed");
+        }
+        djstest.done();
+    };
+
+    var mechanismImplementations = {
+        indexeddb: { factory: datajs.IndexedDBStore, canCreate: canCreateIndexedDb, cleanup: cleanIndexedDbStorage },
+        dom: { factory: datajs.DomStore, canCreate: canCreateDomStore, cleanup: cleanDomStorage },
+        memory: { factory: datajs.MemoryStore, canCreate: canCreateMemoryStore, cleanup: cleanMemoryStorage }
+    };
+
+    var oldWindowOnError;
+
+    for (var mechanism in mechanismImplementations) {
+        module("Unit", {
+            mechanism: mechanism,
+            createStore: function (name) {
+                var store = datajs.createStore(name + "_" + this.mechanism, this.mechanism);
+                this.stores.push(store);
+                return store;
+            },
+            setup: function () {
+                this.stores = [];
+                mechanismImplementations[this.mechanism].cleanup.call(this);
+
+                // FireFox 7.0.1 bubbles an error event when there is an IndexedDB error, even when the error has been handled graciously.
+                // This is a work around to keep QUnit from reporting false failures in IndexedDB negative tests.
+                if (this.mechanism === "indexeddb") {
+                    oldWindowOnError = window.onerror;
+                    window.onerror = null;
+                }
+            },
+            teardown: function () {
+                mechanismImplementations[this.mechanism].cleanup.call(this);
+                this.stores = [];
+
+                // Restore QUnit's onerror handler.
+                if (this.mechanism === "indexeddb") {
+                    window.onerror = oldWindowOnError;
+                }
+            }
+        });
+
+        if (!canCreateStore(mechanism)) {
+            djstest.addTest(function (mechanism) {
+                djstest.expectException(function () {
+                    mechanismImplemenatations[mechanism].factory.create("my horrible not working store");
+                });
+                djstest.done();
+            }, "Local storage mechanism " + mechanism + " not supported by this browser", mechanism);
+        } else {
+
+            djstest.addTest(function storeAddTest(mechanism) {
+                var tuples = [
+                    { key: "null", value: null },
+                    { key: "undefined", value: undefined },
+                    { key: "number", value: 12345.678 },
+                    { key: "string", value: "String value" },
+                    { key: "date", value: new Date() },
+                    { key: "object", value: { p1: 1234, nested: { p1: "a", p2: "b"}} },
+                    { key: "array", value: [1, 2, 3, 4, 5] },
+                    { key: "key1", value: "some value" },
+                    { key: "key1", value: "this should fail", error: true },
+                    { key: ["key", "key2"], value: ["value", "value2"], error: mechanism !== "indexeddb" },
+                    { key: ["key6", "key7", "key6"], value: ["value", "value2", "value3"], error: true }
+                ];
+
+                var store = this.createStore("store1");
+                var job = new djstest.Job();
+
+                $.each(tuples, function (_, tuple) {
+                    job.queue(function task(success, fail) {
+
+                        var unexpectedError = makeUnexpectedErrorHandler(fail);
+                        djstest.log("running task");
+
+                        store.add(tuple.key, tuple.value,
+                            function (key, value) {
+                                djstest.assertAreEqual(key, tuple.key, "Keys match for " + mechanism + " - key = " + key.toString());
+                                djstest.assertAreEqualDeep(value, tuple.value, "Values match for " + mechanism + " - key = " + key.toString());
+
+                                job.queueNext(function (success, fail) {
+                                    store.read(tuple.key, function (key, value) {
+                                        djstest.assertAreEqualDeep(value, tuple.value, "Key: " + key + " is present in the store");
+                                        success();
+                                    }, makeUnexpectedErrorHandler(fail));
+                                });
+                                success();
+                            },
+                            function (err) {
+                                if (!tuple.error) {
+                                    unexpectedError(err);
+                                } else {
+                                    djstest.pass("error handler was called as expected");
+                                    success();
+                                }
+                            });
+                    });
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+
+            }, "Store Add Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeAddOrUpdateTest(mechanism) {
+                var tuples = [
+                    { key: "null", value: null },
+                    { key: "undefined", value: undefined },
+                    { key: "number", value: 12345.678 },
+                    { key: "string", value: "String value" },
+                    { key: "date", value: new Date() },
+                    { key: "object", value: { p1: 1234, nested: { p1: "a", p2: "b"}} },
+                    { key: "array", value: [1, 2, 3, 4, 5] },
+                    { key: "key1", value: "some value" },
+                    { key: "key1", value: "this should not fail" },
+                    { key: ["key", "key2", "key3"], value: ["value", "value2", "value3"], error: mechanism !== "indexeddb" },
+                    { key: ["key", "key2", "key3"], value: ["value4", "value5", "value6"], error: mechanism !== "indexeddb" },
+                    { key: "key1", value: 456 }
+                ];
+
+                var store = this.createStore("store2");
+                var job = new djstest.Job();
+
+                $.each(tuples, function (_, tuple) {
+                    job.queue(function (success, fail) {
+
+                        var unexpectedError = makeUnexpectedErrorHandler(fail);
+
+                        store.addOrUpdate(tuple.key, tuple.value,
+                            function (key, value) {
+                                djstest.assert(!tuple.error, "success should be called");
+                                djstest.assertAreEqual(key, tuple.key, "Keys match");
+                                djstest.assertAreEqualDeep(value, tuple.value, "Values match");
+
+                                store.read(tuple.key, function (key, value) {
+                                    djstest.assertAreEqual(key, tuple.key, "Keys match");
+                                    djstest.assertAreEqualDeep(value, tuple.value, "Values match");
+                                    success();
+                                }, unexpectedError);
+                            },
+                            function (err) {
+                                if (!tuple.error) {
+                                    unexpectedError(err);
+                                } else {
+                                    djstest.pass("error handler was called as expected");
+                                    success();
+                                }
+                            });
+                    });
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Add or Update Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeContainsTest(mechanism) {
+                var store = this.createStore("store3");
+                var job = new djstest.Job();
+
+                job.queue(function (success, fail) {
+                    store.add("Key1", "Some value", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.contains("Key1", function (contained) {
+                        djstest.assert(contained, "Key is present in the store");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.contains("Key2", function (contained) {
+                        djstest.assert(!contained, "Key is not present in the store");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+
+            }, "Store Contains Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeGetAllKeysTest(mechanism) {
+                var store = this.createStore("store4");
+                var store2 = this.createStore("store4_1");
+
+                var expectedKeys = [];
+                var job = new djstest.Job();
+
+                var i;
+                for (i = 1; i <= 20; i++) {
+                    (function (i) {
+                        job.queue(function (success, fail) {
+                            store.add(i.toString(), "value" + i, success, makeUnexpectedErrorHandler(fail));
+                        });
+
+                        job.queue(function (success, fail) {
+                            store2.add((i + 20).toString(), "value" + (i + 20), success, makeUnexpectedErrorHandler(fail));
+                        });
+                    })(i);
+
+                    expectedKeys.push(i.toString());
+                }
+
+                job.queue(function (success, fail) {
+                    store.getAllKeys(function (keys) {
+                        expectedKeys.sort();
+                        keys.sort();
+                        djstest.assertAreEqualDeep(keys, expectedKeys, "All expected keys where returned");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    store2.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Get All Keys Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeReadTest(mechanism) {
+                var tuples = [
+                    { key: "null", value: null },
+                    { key: "undefined", value: undefined },
+                    { key: "number", value: 12345.678 },
+                    { key: "string", value: "String value" },
+                    { key: "date", value: new Date() },
+                    { key: "dateOffset", value: (function () {
+                        var d = new Date();
+                        d.__type = "Edm.DateTimeOffset";
+                        d.__offset = "+03:30";
+                        return d;
+                    })()
+                    },
+                    { key: "complexDate", value: (function () {
+                        var d = new Date();
+                        d.nestedDate = new Date();
+                        d.nestedDate.__type = "Edm.DateTimeOffset";
+                        d.nestedDate.__offset = "+03:30";
+                        return d;
+                    })()
+                    },
+                    { key: "object", value: { p1: 1234, nested: { p1: "a", p2: "b", p3: new Date()}} },
+                    { key: "array", value: [1, 2, 3, 4, 5] }
+                ];
+
+                var store = this.createStore("store5");
+                var job = new djstest.Job();
+
+                $.each(tuples, function (_, tuple) {
+                    job.queue(function (success, fail) {
+                        store.add(tuple.key, tuple.value,
+                            function () {
+                                job.queue(function (success, fail) {
+                                    store.read(tuple.key, function (key, value) {
+                                        djstest.assertAreEqual(key, tuple.key, "Keys match");
+                                        djstest.assertAreEqualDeep(value, tuple.value, "Values match");
+                                        success();
+                                    }, makeUnexpectedErrorHandler(fail));
+                                });
+                                success();
+                            },
+                           function (err) {
+                               if (!tuple.error) {
+                                   djstest.fail(err.message);
+                                   fail();
+                               } else {
+                                   djstest.pass("error handler was called as expected");
+                                   success();
+                               }
+                           });
+                    });
+                });
+
+                job.queue(function (success, fail) {
+                    store.read("Unknown key", function (key, value) {
+                        djstest.assertAreEqual(value, undefined, "Store get returns undefined for keys that do not exist in the store");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+
+            }, "Store Read Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeReadArrayTest(mechanism) {
+                var makeError = function (success, fail) {
+                    return function (err) {
+                        if (mechanism !== "indexeddb") {
+                            djstest.pass("Error callback called as expected");
+                            success();
+                        } else {
+                            djstest.fail(err.message);
+                            fail();
+                        }
+                    };
+                };
+
+                var store = this.createStore("store6");
+                var job = new djstest.Job();
+
+                job.queue(function (success, fail) {
+                    store.add(["key", "key2", "key3"], ["value", "value2", "value3"], success, makeError(success, fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.read(["key", "key2", "key3"], function (keys, values) {
+                        djstest.assertAreEqualDeep(keys, ["key", "key2", "key3"]);
+                        djstest.assertAreEqualDeep(values, ["value", "value2", "value3"]);
+                        success();
+                    }, makeError(success, fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.read(["key", "badkey"], function (keys, values) {
+                        djstest.assertAreEqualDeep(keys, ["key", "badkey"]);
+                        djstest.assertAreEqualDeep(values, ["value", undefined]);
+                        success();
+                    }, makeError(success, fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Read Array Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeRemoveTest(mechanism) {
+                var store = this.createStore("store7");
+                var job = new djstest.Job();
+
+                job.queue(function (success, fail) {
+                    store.add("Key1", "Some value", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.add("Key2", "Some value", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.remove("Key1", function () {
+                        djstest.pass("Key1 was removed from the store")
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.contains("Key1", function (contained) {
+                        djstest.assert(!contained, "Key1 is not present in the store");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.remove("Key that has never been added", function () {
+                        djstest.pass('"Key that has never been added" was removed from the store');
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.contains("Key2", function (contained) {
+                        djstest.assert(contained, "Key2 is present in the store");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Remove Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeUpdateTest(mechanism) {
+                var store = this.createStore("store8");
+
+                var startKey = "Key1";
+                var startValue = "start value";
+                var updateKey = "Key2";
+                var updatedValue = "updated value";
+
+                var job = new djstest.Job();
+
+                job.queue(function (success, fail) {
+                    store.add(startKey, startValue, success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.add(updateKey, startValue, success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.update(updateKey, updatedValue, function (key, value) {
+                        djstest.assertAreEqual(key, updateKey, "Updated keys match");
+                        djstest.assertAreEqualDeep(value, updatedValue, "Updated values match");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.read(updateKey, function (key, value) {
+                        djstest.assertAreEqual(key, updateKey, "Updated keys match after get");
+                        djstest.assertAreEqualDeep(value, updatedValue, "Updated values match after get");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.read(startKey, function (key, value) {
+                        djstest.assertAreEqual(key, startKey, "Non updated keys match after get");
+                        djstest.assertAreEqualDeep(value, startValue, "Non updated values match after get");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Update Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeClearTest(mechanism) {
+                var store = this.createStore("store9");
+                var store2 = this.createStore("store9_1");
+
+                var job = new djstest.Job();
+                job.queue(function (success, fail) {
+                    store.add("Key1", "value in store", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.add("Key2", "value in store", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.add("Key3", "value in store", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store2.add("Key1", "value in store2", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.clear(function () {
+                        djstest.pass("Store was cleared");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.contains("Key1", function (contained) {
+                        djstest.assert(!contained, "Key1 was removed from store");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store2.contains("Key1", function (contained) {
+                        djstest.assert(contained, "Key1 still exists in store 2");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    store2.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Clear Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeUpdateNonExistentTest(mechanism) {
+                var store = this.createStore("store10");
+                var job = new djstest.Job();
+
+                job.queue(function (success, fail) {
+                    store.add("key", "value", success, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.update("badKey", "new value",
+                        function () {
+                            djstest.fail("Sucess handler called when not expected");
+                            fail();
+                        },
+                        function (err) {
+                            djstest.pass("Error callback called as expexted");
+                            success();
+                        });
+                });
+
+                job.queue(function (success, fail) {
+                    store.update(["key", "badkey"], ["value", "badvalue"],
+                        function () {
+                            djstest.fail("Sucess handler called when not expected");
+                            fail();
+                        },
+                        function (err) {
+                            djstest.pass("Error callback called as expected");
+                            success();
+                        });
+                });
+
+                job.queue(function (success, fail) {
+                    store.read("key", function (key, value) {
+                        djstest.assertAreEqual(value, "value", "value was not changed");
+                        success();
+                    }, makeUnexpectedErrorHandler(fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Update Non-Existent Test with mechanism " + mechanism, mechanism);
+
+            djstest.addTest(function storeUpdateArrayTest(mechanism) {
+                var makeError = function (success, fail) {
+                    return function (err) {
+                        if (mechanism !== "indexeddb") {
+                            djstest.pass("Error callback called as expected");
+                            success();
+                        } else {
+                            djstest.fail(err.message);
+                            fail();
+                        }
+                    };
+                };
+
+                var store = this.createStore("store11");
+                var job = new djstest.Job();
+
+                job.queue(function (success, fail) {
+                    store.add(["key", "key2"], ["value1", "value2"], success, makeError(success, fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.update(["key", "key2"], ["value1", "value4"], success, makeError(success, fail));
+                });
+
+                job.queue(function (success, fail) {
+                    store.read(["key", "key2"], function (key, value) {
+                        djstest.assertAreEqualDeep(value, ["value1", "value4"], "value was not changed");
+                        success();
+                    }, makeError(success, fail));
+                });
+
+                job.run(function (succeeded) {
+                    store.close();
+                    testJobDone(succeeded);
+                });
+            }, "Store Update Array Test with mechanism " + mechanism, mechanism);
+        }
+    }
+
+    module("Unit");
+
+    djstest.addTest(function CreateStoreTest() {
+        var defaultExpected = canCreateDomStore() ? "dom" : "memory";
+        var tests = [
+            { mechanism: "dom", exception: !canCreateDomStore(), expected: "dom" },
+            { mechanism: "memory", exception: false, expected: "memory" },
+            { mechanism: "", exception: false, expected: defaultExpected },
+            { mechanism: null, exception: false, expected: defaultExpected },
+            { mechanism: "unknown", exception: true }
+       ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            try {
+                var test = tests[i];
+                var store = datajs.createStore("testStore" + i, tests[i].mechanism);
+
+                if (!test.exception) {
+                    djstest.assertAreEqual(store.mechanism, test.expected, "Created store of the expected mechanism");
+                } else {
+                    djstest.fail("Didn't get the expected exception");
+                }
+            }
+            catch (e) {
+                djstest.assert(test.exception, "Expected exception");
+            }
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function CreateBestStoreTest() {
+        var bestMechanism;
+
+        for (var name in mechanismImplementations) {
+            if (!bestMechanism && canCreateStore(name) && name !== "indexeddb") {
+                bestMechanism = name;
+            }
+        }
+
+        if (bestMechanism) {
+            var tests = [
+                "best",
+                undefined
+            ];
+
+            for (var i in tests) {
+                var store = datajs.createStore("best store ever " + i, tests[i]);
+                djstest.assertAreEqual(store.mechanism, bestMechanism, "Mechanisms match");
+            }
+        } else {
+            djstest.pass("This browser doesn't support any of the implemented local storage mechanisms");
+        }
+        djstest.done();
+    });
+
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/test-manager.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/test-manager.html b/JSLib/tests/test-manager.html
new file mode 100644
index 0000000..fa4911a
--- /dev/null
+++ b/JSLib/tests/test-manager.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <title>datajs test manager</title>
+    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.1.js"></script>
+    <script type="text/javascript" src="test-list.js"></script>
+    <script type="text/javascript">
+        var serviceRoot = "./common/TestLogger.svc/";
+        $(function () {
+            $(getAllTestFiles()).each(function () {
+                $("#pages").append("<input type='checkbox' name='page' value='" + this + "' checked />" + this + "<br />");
+            });
+            refreshActiveRuns();
+        });
+
+        var createTestRun = function (form) {
+            $.getJSON(serviceRoot + "CreateTestRun", function (data) {
+                var testRunId = data.d;
+
+                // Build pages list
+                var pages = [];
+                $(form).find("input[name='page']:checked").each(function () {
+                    pages.push(this.value);
+                });
+
+                var firstPage = pages[0];
+                pages.shift();
+
+                $.get(serviceRoot + "MarkInProgress?testRunId=" + testRunId, function () {
+                    $.get(serviceRoot + "SetTestNamePrefix?testRunId=" + testRunId + "&prefix=" + $("#browser").val() + "-", function () {
+                        var renderLinks = function () {
+                            $("#runLink").attr("href", firstPage + "?testRunId=" + testRunId);
+                            $("#runLink").text(testRunId);
+                            $("#resultsLink").attr("href", serviceRoot + "GetTestRunResults?testRunId=" + testRunId);
+                            $("#resultsLink").text(testRunId);
+                            refreshActiveRuns();
+                        };
+
+                        if (pages.length > 0) {
+                            $.get(serviceRoot + "AddTestPages?testRunId=" + testRunId + "&pages=" + pages.join(","), renderLinks);
+                        }
+                        else {
+                            renderLinks();
+                        }
+                    });
+                });
+            });
+        };
+
+        var refreshActiveRuns = function () {
+            $("#activeRuns").empty();
+            $.getJSON(serviceRoot + "GetActiveTestRuns", function (data) {
+                if (data.d.length === 0) {
+                    $("#activeRuns").text("There are no active runs");
+                } else {
+                    $.each(data.d, function (_, id) {
+                        $("#activeRuns").append("<a href='" + serviceRoot + "GetTestRunResults?testRunId=" + id + "'>" + id + "</a><br />");
+                    })
+                };
+            });
+        };
+    </script>
+</head>
+<body>
+    <h1>datajs test manager</h1>
+    <table style="width:100%"><tr><td style="vertical-align:top">
+        <h4>1. Create Test Run</h4>
+        <form onsubmit="createTestRun(this); return false;">
+            <div>Pages</div>
+            <div id="pages"></div>
+            <br />
+            <div>Browser: <input type="text" id="browser" /></div>
+            <br />
+            <input type="submit" value="Create Test Run" />
+        </form>
+
+        <h4>2. Run Tests</h4>
+        Test Run ID: <a id="runLink"></a>
+
+        <h4>3. View Results</h4>
+        Test Run ID: <a id="resultsLink"></a>
+
+    </td><td style="vertical-align:top">
+        <h4>Active Runs <input type="button" value="Refresh" onclick="refreshActiveRuns()" /></h4>
+        <div id="activeRuns"></div>
+    </td></tr></table>
+</body>
+</html>
\ No newline at end of file


[05/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-json-light-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-json-light-tests.js b/JSLib/tests/odata-json-light-tests.js
new file mode 100644
index 0000000..a00e941
--- /dev/null
+++ b/JSLib/tests/odata-json-light-tests.js
@@ -0,0 +1,2479 @@
+/// <reference path="../src/odata-net.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="../src/odata-json-light.js" />
+/// <reference path="../src/odata-json.js" />
+/// <reference path="../common/djstest.js" />
+
+// odata-json-light-tests.js
+
+(function (window, undefined) {
+
+    // DATAJS INTERNAL START
+
+    var mockHttpClient;
+
+    module("Unit", {
+        setup: function () {
+            mockHttpClient = window.MockHttpClient.clear();
+        }
+    });
+
+    var getSampleModel = function () {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "NS",
+                    "entityType": [{
+                        "name": "EntityType",
+                        "key": { "propertyRef": [{ "name": "Id"}] },
+                        "property": [
+                                { "name": "Id", "type": "Edm.String" },
+                                { "name": "P1", "type": "Edm.Int32" }
+                         ]
+                    }],
+                    entityContainer: [{
+                        name: "TestContainer",
+                        isDefaultEntityContainer: "true",
+                        entitySet: [{ "name": "EntityTypes", "entityType": "NS.EntityType"}]
+                    }]
+                }]
+            }
+        };
+        return testModel;
+    };
+
+    var getSampleModelWithTwoKeys = function () {
+        var testModel = getSampleModel();
+        testModel.dataServices.schema[0].entityType[0].key = { "propertyRef": [{ "name": "Id" }, { "name": "P1"}] };
+        return testModel;
+    };
+
+    var getSampleModelWithOneConcurrencyProperty = function () {
+        var testModel = getSampleModel();
+        testModel.dataServices.schema[0].entityType[0].property[0].concurrencyMode = "Fixed";
+        return testModel;
+    };
+    
+    var getSampleModelWithOneBinaryConcurrencyProperty = function () {
+        var testModel = getSampleModel();
+        testModel.dataServices.schema[0].entityType[0].property[1].concurrencyMode = "Fixed";
+        testModel.dataServices.schema[0].entityType[0].property[1].type = "Edm.Binary";
+        return testModel;
+    };
+
+    var getSampleModelWithMultipleConcurrencyProperties = function () {
+        var testModel = getSampleModel();
+        testModel.dataServices.schema[0].entityType[0].property[0].concurrencyMode = "Fixed";
+        testModel.dataServices.schema[0].entityType[0].property[1].concurrencyMode = "Fixed";
+        return testModel;
+    };
+
+    var getSampleModelWithDateTimeConcurrencyProperties = function () {
+        var testModel = getSampleModel();
+        testModel.dataServices.schema[0].entityType[0].property[1] = { "name": "P1", "type": "Edm.DateTime", concurrencyMode: "Fixed" };
+        return testModel;
+    };
+    
+    var getSampleModelWithDecimalProperty = function () {
+        var testModel = getSampleModel();
+        testModel.dataServices.schema[0].entityType[0].property[1] = { "name": "P1", "type": "Edm.Decimal"};
+        return testModel;
+    };
+
+    var getSampleModelWithNavPropertiesAndInheritedTypes = function () {
+        return {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "maxDataServiceVersion": "4.0",
+                "schema": [
+                    {
+                        "namespace": "ODataDemo",
+                        "entityType": [
+                            {
+                                "name": "Product",
+                                "key": {
+                                    "propertyRef": [
+                                        {
+                                            "name": "ID"
+                                        }
+                                    ]
+                                },
+                                "property": [
+                                    {
+                                        "name": "ID",
+                                        "nullable": "false",
+                                        "type": "Edm.Int32"
+                                    }
+                                ],
+                                "navigationProperty": [
+                                    {
+                                        "name": "Category",
+                                        "toRole": "Category_Products",
+                                        "fromRole": "Product_Category",
+                                        "relationship": "ODataDemo.Product_Category_Category_Products"
+                                    }]
+                            },
+                            {
+                                "name": "FeaturedProduct",
+                                "baseType": "ODataDemo.Product",
+                                "navigationProperty": [
+                                    {
+                                        "name": "Advertisement",
+                                        "toRole": "Advertisement_FeaturedProduct",
+                                        "fromRole": "FeaturedProduct_Advertisement",
+                                        "relationship": "ODataDemo.FeaturedProduct_Advertisement_Advertisement_FeaturedProduct"
+                                    }
+                                ]
+                            },
+                            {
+                                "name": "Advertisement",
+                                "key": {
+                                    "propertyRef": [
+                                        {
+                                            "name": "ID"
+                                        }
+                                    ]
+                                },
+                                "property": [
+                                    {
+                                        "name": "ID",
+                                        "nullable": "false",
+                                        "type": "Edm.Guid"
+                                    }
+                                ],
+                                "navigationProperty": [
+                                    {
+                                        "name": "FeaturedProduct",
+                                        "toRole": "FeaturedProduct_Advertisement",
+                                        "fromRole": "Advertisement_FeaturedProduct",
+                                        "relationship": "ODataDemo.FeaturedProduct_Advertisement_Advertisement_FeaturedProduct"
+                                    }
+                                ]
+                            },
+                            {
+                                "name": "Category",
+                                "key": {
+                                    "propertyRef": [
+                                        {
+                                            "name": "ID"
+                                        }
+                                    ]
+                                },
+                                "property": [
+                                    {
+                                        "name": "ID",
+                                        "nullable": "false",
+                                        "type": "Edm.Int32"
+                                    }
+                                ],
+                                "navigationProperty": [
+                                    {
+                                        "name": "Products",
+                                        "toRole": "Product_Category",
+                                        "fromRole": "Category_Products",
+                                        "relationship": "ODataDemo.Product_Category_Category_Products"
+                                    }
+                                ]
+                            }
+                        ],
+                        "association": [
+                            {
+                                "name": "Product_Category_Category_Products",
+                                "end": [
+                                    {
+                                        "type": "ODataDemo.Category",
+                                        "multiplicity": "0..1",
+                                        "role": "Category_Products"
+                                    },
+                                    {
+                                        "type": "ODataDemo.Product",
+                                        "multiplicity": "*",
+                                        "role": "Product_Category"
+                                    }
+                                ]
+                            },
+                            {
+                                "name": "FeaturedProduct_Advertisement_Advertisement_FeaturedProduct",
+                                "end": [
+                                    {
+                                        "type": "ODataDemo.Advertisement",
+                                        "multiplicity": "0..1",
+                                        "role": "Advertisement_FeaturedProduct"
+                                    },
+                                    {
+                                        "type": "ODataDemo.FeaturedProduct",
+                                        "multiplicity": "0..1",
+                                        "role": "FeaturedProduct_Advertisement"
+                                    }
+                                ]
+                            }
+                        ],
+                        "entityContainer": [
+                            {
+                                "name": "DemoService",
+                                "isDefaultEntityContainer": "true",
+                                "entitySet": [
+                                    {
+                                        "name": "Products",
+                                        "entityType": "ODataDemo.Product"
+                                    },
+                                    {
+                                        "name": "Advertisements",
+                                        "entityType": "ODataDemo.Advertisement"
+                                    },
+                                    {
+                                        "name": "Categories",
+                                        "entityType": "ODataDemo.Category"
+                                    }
+                                ],
+                                "associationSet": [
+                                    {
+                                        "name": "Products_Advertisement_Advertisements",
+                                        "association": "ODataDemo.FeaturedProduct_Advertisement_Advertisement_FeaturedProduct",
+                                        "end": [
+                                            {
+                                                "role": "FeaturedProduct_Advertisement",
+                                                "entitySet": "Products"
+                                            },
+                                            {
+                                                "role": "Advertisement_FeaturedProduct",
+                                                "entitySet": "Advertisements"
+                                            }
+                                        ]
+                                    },
+                                    {
+                                        "name": "Products_Category_Categories",
+                                        "association": "ODataDemo.Product_Category_Category_Products",
+                                        "end": [
+                                            {
+                                                "role": "Product_Category",
+                                                "entitySet": "Products"
+                                            },
+                                            {
+                                                "role": "Category_Products",
+                                                "entitySet": "Categories"
+                                            }
+                                        ]
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                ]
+            }
+        };
+    };
+
+    var failTest = function (err) {
+        if (err && err.message) {
+            djstest.fail(err.message);
+        } else {
+            djstest.fail("unexpected failure");
+        }
+        djstest.done();
+    };
+
+    var verifySerializedJsonLightData = function (actual, expected, message, requestUri) {
+        mockHttpClient.addRequestVerifier("*", function (request) {
+            djstest.assertAreEqualDeep(JSON.parse(request.body), expected, message);
+            djstest.done();
+        });
+
+        OData.request({
+            requestUri: requestUri || "http://someUri",
+            headers: { "Content-Type": "application/json" },
+            method: "PUT",
+            data: actual
+        }, null, failTest, null, mockHttpClient);
+    };
+
+    var verifyReadJsonLightData = function (input, expected, message, model) {
+        var response = { headers: { "Content-Type": "application/json;odata.metadata=full", DataServiceVersion: "4.0" }, body: JSON.stringify(input) };
+
+        OData.jsonHandler.read(response, { metadata: model });
+        djstest.assertAreEqualDeep(response.data, expected, message);
+    };
+
+    var verifyReadJsonLightWithMinimalMetadata = function (input, expected, message, model) {
+        var response = { headers: { "Content-Type": "application/json;odata.metadata=minimal", DataServiceVersion: "4.0" }, body: JSON.stringify(input) };
+
+        OData.jsonHandler.read(response, { metadata: model });
+        djstest.assertAreEqualDeep(response.data, expected, message);
+    };
+
+
+    djstest.addTest(function jsonLightReadEmptySvcDocTest() {
+        var input = {
+            "odata.metadata": "http://someUri/ODataService.svc/OData/$metadata",
+            "value": []
+        };
+
+        var expected = {
+            workspaces: [
+                {
+                    collections: []
+                }
+            ]
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light links document was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadSvcDocTest() {
+        var input = {
+            "odata.metadata": "http://someUri/ODataService.svc/OData/$metadata",
+            "value": [
+                { "name": "Customers", "url": "Customers" },
+                { "name": "Employees", "url": "http://someUri/ODataService.svc/OData/Employees" }
+            ]
+        };
+
+        var expected = {
+            workspaces: [
+                {
+                    collections: [
+                        {
+                            title: "Customers",
+                            href: "http://someUri/ODataService.svc/OData/Customers"
+                        },
+                        {
+                            title: "Employees",
+                            href: "http://someUri/ODataService.svc/OData/Employees"
+                        }
+                    ]
+                }
+            ]
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light links document was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadEntryMetadataUriTest() {
+        var tests = {
+            "Qualified entity set": {
+                input: { "odata.metadata": "http://someUri/$metadata#Ns.Container.EntitySet/@Element" },
+                expected: { __metadata: { type: null} }
+            },
+            "Unqualified entity set": {
+                input: { "odata.metadata": "http://someUri/$metadata#Ns.Container.EntitySet/@Element" },
+                expected: { __metadata: { type: null} }
+            },
+            "Qualified entity set with type cast": {
+                input: { "odata.metadata": "http://someUri/$metadata#Ns.Container.EntitySet/TypeCast/@Element" },
+                expected: { __metadata: { type: "TypeCast"} }
+            },
+
+            "Unqualified entity set with type cast": {
+                input: { "odata.metadata": "http://someUri/$metadata#EntitySet/TypeCast/@Element" },
+                expected: { __metadata: { type: "TypeCast"} }
+            }
+        };
+
+        for (name in tests) {
+            var test = tests[name];
+            verifyReadJsonLightData(test.input, test.expected, name + " - Json light entry metadata uri was read properly.");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadEntryMetadataUriWithMetadataDocumentTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Test.Catalog",
+                    "entityContainer": [{
+                        "name": "TestCatalog",
+                        "isDefaultEntityContainer": "true",
+                        "entitySet": [
+                           { "name": "Titles", "entityType": "TestCatalog.Model.Title" }
+                    ]
+                    }, {
+                        "name": "GenreCatalog",
+                        "isDefaultEntityContainer": "false",
+                        "entitySet": [
+                           { "name": "Genres", "entityType": "TestCatalog.Model.Genre" }
+                    ]
+                    }]
+                }]
+            }
+        };
+
+        var tests = {
+            "Qualified entity set": {
+                input: { "odata.metadata": "http://someUri/$metadata#Test.Catalog.GenreCatalog.Genres/@Element" },
+                expected: { __metadata: { type: "TestCatalog.Model.Genre"} }
+            },
+            "Unqualified entity set": {
+                input: { "odata.metadata": "http://someUri/$metadata#Titles/@Element" },
+                expected: { __metadata: { type: "TestCatalog.Model.Title"} }
+            },
+            "Qualified entity set with type cast": {
+                input: { "odata.metadata": "http://someUri/$metadata#Test.Catalog.Titles/TestCatalog.Model.Songs/@Element" },
+                expected: { __metadata: { type: "TestCatalog.Model.Songs"} }
+            },
+            "Unqualified entity set with type cast": {
+                input: { "odata.metadata": "http://someUri/$metadata#Titles/TestCatalog.Model.Songs/@Element" },
+                expected: { __metadata: { type: "TestCatalog.Model.Songs"} }
+            },
+            "Unqualified entity set in non default entity container": {
+                input: { "odata.metadata": "http://someUri/$metadata#Generes/@Element" },
+                expected: { __metadata: { type: null} }
+            },
+            "Unqualified entity set in non default entity container with type cast": {
+                input: { "odata.metadata": "http://someUri/$metadata#Generes/TypeCast/@Element" },
+                expected: { __metadata: { type: "TypeCast"} }
+            }
+        };
+
+        for (name in tests) {
+            var test = tests[name];
+            verifyReadJsonLightData(test.input, test.expected, name + " - Json light entry metadata uri was read properly.", testModel);
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadTypeInPayloadWinsOverModelTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "NS",
+                    "entityType": [{
+                        "name": "EntityType",
+                        "hasStream": "true",
+                        "key": { "propertyRef": [{ "name": "Id"}] },
+                        "property": [
+                                { "name": "Id", "type": "Edm.String" },
+                                { "name": "P1", "type": "Edm.String" },
+                                { "name": "P2", "type": "NS.ComplexType" },
+                                { "name": "P3", "type": "Edm.Int64" }
+                            ]
+                    }],
+                    "complexType": [{
+                        "name": "ComplexType",
+                        "property": [
+                            { "name": "C1", "type": "Edm.String" }
+                        ]
+                    }]
+                }]
+            }
+        };
+
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/NS.EntityType/@Element",
+            P1: "Hello",
+            P2: {
+                "odata.type": "Instance",
+                C1: "World"
+            },
+            "P2@odata.type": "Property",
+            P3: "500",
+            "P3@odata.type": "Property",
+            P4: {
+                "odata.type": "NS.ComplexType",
+                C1: "!!"
+            }
+        };
+
+        var expected = {
+            __metadata: {
+                type: "NS.EntityType",
+                properties: {
+                    P1: { type: "Edm.String" },
+                    P2: {
+                        type: "Instance",
+                        properties: {
+                            C1: { type: null }
+                        }
+                    },
+                    P3: { type: "Property" },
+                    P4: {
+                        type: "NS.ComplexType",
+                        properties: {
+                            C1: { type: "Edm.String" }
+                        }
+                    }
+                }
+            },
+            P1: "Hello",
+            P2: {
+                __metadata: {
+                    type: "Instance"
+                },
+                C1: "World"
+            },
+            P3: "500",
+            P4: {
+                __metadata: {
+                    type: "NS.ComplexType"
+                },
+                C1: "!!"
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadObjectWithCollectionPropertyTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "NS",
+                    "entityType": [{
+                        "name": "EntityType",
+                        "property": [
+                                { "name": "p1", "type": "Collection(Edm.Int32)" }
+                            ]
+                    }]
+                }]
+            }
+        };
+
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/NS.EntityType/@Element",
+            p1: [1, 2, 3],
+            p2: [
+                { c1: 100, c2: 200 },
+                { c1: 400, c2: 500, "odata.type": "NS.OtherType" }
+            ],
+            "p2@odata.type": "Collection(NS.ComplexType)",
+            p3: [5, 6, 7]
+        };
+
+        var expected = {
+            __metadata: {
+                type: "NS.EntityType",
+                properties: {
+                    p1: { type: "Collection(Edm.Int32)" },
+                    p2: {
+                        type: "Collection(NS.ComplexType)",
+                        elements: [
+                            {
+                                type: "NS.ComplexType",
+                                properties: {
+                                    c1: { type: null },
+                                    c2: { type: null }
+                                }
+                            },
+                            {
+                                type: "NS.OtherType",
+                                properties: {
+                                    c1: { type: null },
+                                    c2: { type: null }
+                                }
+                            }
+                        ]
+                    },
+                    p3: { type: null }
+                }
+            },
+            p1: {
+                __metadata: { type: "Collection(Edm.Int32)" },
+                results: [1, 2, 3]
+            },
+            p2: {
+                __metadata: { type: "Collection(NS.ComplexType)" },
+                results: [
+                    {
+                        __metadata: {
+                            type: "NS.ComplexType"
+                        },
+                        c1: 100,
+                        c2: 200
+                    },
+                    {
+                        __metadata: {
+                            type: "NS.OtherType"
+                        },
+                        c1: 400,
+                        c2: 500
+                    }
+                ]
+            },
+            p3: {
+                __metadata: { type: null },
+                results: [5, 6, 7]
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light object with collection porperties was read properly", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadEntryODataAnnotationsTest() {
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#NS.Container.Set/TypeCast/@Element",
+            "odata.id": "Customers(1)",
+            "odata.etag": "etag-value",
+            "odata.readLink": "read/Customers(1)",
+            "odata.editLink": "Customers(1)",
+            "odata.mediaReadLink": "Customers(1)/Image",
+            "odata.mediaEditLink": "Customers(1)/$value",
+            "odata.mediaETag": "stream-etag-value",
+            "odata.mediaContentType": "image/jpg"
+        };
+
+        var expected = {
+            __metadata: {
+                id: "Customers(1)",
+                type: "TypeCast",
+                etag: "etag-value",
+                self: "http://someUri/read/Customers(1)",
+                edit: "http://someUri/Customers(1)",
+                uri: "http://someUri/Customers(1)",
+                media_src: "http://someUri/Customers(1)/Image",
+                edit_media: "http://someUri/Customers(1)/$value",
+                media_etag: "stream-etag-value",
+                content_type: "image/jpg"
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light entry OData annotations were read properly");
+        djstest.done();
+    });
+
+
+    djstest.addTest(function jsonLightReadObjectWithComplexPropertyTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Ns",
+                    "entityType": [{
+                        "name": "EntityType",
+                        "property": [
+                                { "name": "p1", "type": "Ns.ComplexType" }
+                         ]
+                    }],
+                    "complexType": [{
+                        "name": "ComplexType",
+                        "property": [
+                            { "name": "c1", "type": "Edm.Int16" },
+                            { "name": "c2", "type": "Edm.Int32" },
+                            { "name": "c3", "type": "Ns.ComplexType" }
+                        ]
+                    }]
+                }]
+            }
+        };
+
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/Ns.EntityType/@Element",
+            p1: {
+                c1: 100,
+                c2: 200,
+                c3: {
+                    c1: 300,
+                    c2: 400,
+                    c3: {
+                        c1: 500,
+                        c2: 600,
+                        c3: null
+                    }
+                }
+            },
+            p2: {
+                "odata.type": "Ns.ComplexType2",
+                c4: 800,
+                "c4@odata.type": "Edm.Single",
+                c5: 900,
+                c6: {
+                    c1: 1000,
+                    c2: 2000,
+                    c3: {
+                        c1: 1100,
+                        "c1@odata.type": "Edm.Double",
+                        c2: 1200,
+                        c3: null
+                    }
+                },
+                "c6@odata.type": "Ns.ComplexType"
+            },
+            p3: {},
+            "p3@odata.type": "Ns.ComplexType3"
+        };
+
+        var expected = {
+            __metadata: {
+                type: "Ns.EntityType",
+                properties: {
+                    p1: {
+                        type: "Ns.ComplexType",
+                        properties: {
+                            c1: { type: "Edm.Int16" },
+                            c2: { type: "Edm.Int32" },
+                            c3: {
+                                type: "Ns.ComplexType",
+                                properties: {
+                                    c1: { type: "Edm.Int16" },
+                                    c2: { type: "Edm.Int32" },
+                                    c3: {
+                                        type: "Ns.ComplexType",
+                                        properties: {
+                                            c1: { type: "Edm.Int16" },
+                                            c2: { type: "Edm.Int32" },
+                                            c3: { type: "Ns.ComplexType" }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    p2: {
+                        type: "Ns.ComplexType2",
+                        properties: {
+                            c4: { type: "Edm.Single" },
+                            c5: { type: null },
+                            c6: {
+                                type: "Ns.ComplexType",
+                                properties: {
+                                    c1: { type: "Edm.Int16" },
+                                    c2: { type: "Edm.Int32" },
+                                    c3: {
+                                        type: "Ns.ComplexType",
+                                        properties: {
+                                            c1: { type: "Edm.Double" },
+                                            c2: { type: "Edm.Int32" },
+                                            c3: { type: "Ns.ComplexType" }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    p3: { type: "Ns.ComplexType3" }
+                }
+            },
+            p1: {
+                __metadata: { type: "Ns.ComplexType" },
+                c1: 100,
+                c2: 200,
+                c3: {
+                    __metadata: { type: "Ns.ComplexType" },
+                    c1: 300,
+                    c2: 400,
+                    c3: {
+                        __metadata: { type: "Ns.ComplexType" },
+                        c1: 500,
+                        c2: 600,
+                        c3: null
+                    }
+                }
+            },
+            p2: {
+                __metadata: { type: "Ns.ComplexType2" },
+                c4: 800,
+                c5: 900,
+                c6: {
+                    __metadata: { type: "Ns.ComplexType" },
+                    c1: 1000,
+                    c2: 2000,
+                    c3: {
+                        __metadata: { type: "Ns.ComplexType" },
+                        c1: 1100,
+                        c2: 1200,
+                        c3: null
+                    }
+                }
+            },
+            p3: { __metadata: { type: "Ns.ComplexType3"} }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light object with complex type properties was read properly", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadObjectWithNamedStreamProperty() {
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#NS.Container.Set/TypeCast/@Element",
+            "p1@odata.mediaReadLink": "Customers(1)/namedStream",
+            "p1@odata.mediaEditLink": "Customers(1)/namedStream/$value",
+            "p1@odata.mediaETag": "voice-etag-value",
+            "p1@odata.mediaContentType": "audio/basic"
+        };
+
+        var expected = {
+            __metadata: {
+                type: "TypeCast",
+                properties: {
+                    p1: { type: null }
+                }
+            },
+            p1: {
+                __mediaresource: {
+                    media_src: "http://someUri/Customers(1)/namedStream",
+                    edit_media: "http://someUri/Customers(1)/namedStream/$value",
+                    media_etag: "voice-etag-value",
+                    content_type: "audio/basic"
+                }
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light object with named stream properties was read properly");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadEntryWithDeferredNavigationPropertyTests() {
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#NS.Container.Set/TypeCast/@Element",
+            "p1@odata.navigationLinkUrl": "entitySet(1)/deferred",
+            "p1@odata.associationLinkUrl": "entitySet(1)/$links/deferred"
+        };
+
+        var expected = {
+            __metadata: {
+                type: "TypeCast",
+                properties: {
+                    p1: {
+                        type: null,
+                        associationLinkUrl: "http://someUri/entitySet(1)/$links/deferred"
+                    }
+                }
+            },
+            p1: {
+                __deferred: {
+                    uri: "http://someUri/entitySet(1)/deferred"
+                }
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light object with deferred navigation properties was read properly");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadEntryWithInlinedNavigationPropertiesTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Ns",
+                    "entityType": [{
+                        "name": "EntityType",
+                        "navigationProperty": [
+                            { "name": "p1", "relationship": "Ns.Rel1", "toRole": "p1s" },
+                            { "name": "p2", "relationship": "Ns.Rel2", "toRole": "p2s" }
+                        ]
+                    }],
+                    "association": [{
+                        "name": "Rel1",
+                        "end": [
+                            { "type": "Ns.EntityType1", "role": "p1s" },
+                            { "type": "Ns.EntityType", "role": "otherRole" }
+                         ]
+                    },
+                    {
+                        "name": "Rel2",
+                        "end": [
+                            { "type": "Ns.EntityType", "role": "otherRole" },
+                            { "type": "Ns.EntityType2", "role": "p2s" }
+                         ]
+                    }]
+                }]
+            }
+        };
+
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/Ns.EntityType/@Element",
+            p1: [
+                { e1: 100, e2: 200 },
+                { e1: 110, e2: 220 }
+            ],
+            "p1@odata.count": 50,
+            "p1@odata.nextLink": "nextToken",
+            p2: {
+                e3: 300
+            },
+            p3: [
+                { e4: 400, e5: 500 },
+                { e4: 440, e5: 550 }
+            ],
+            "p3@odata.navigationLinkUrl": "http://someUri/entitySet(1)/p3",
+            "p3@odata.associationLinkUrl": "http://someUri/entitySet(1)/$links/p3",
+            p4: {
+                e6: 600
+            },
+            "p4@odata.navigationLinkUrl": "http://someUri/entitySet(1)/p4",
+            "p4@odata.associationLinkUrl": "http://someUri/entitySet(1)/$links/p4",
+            p5: [
+                {
+                    "odata.id": 12345,
+                    e7: 700,
+                    e8: 800
+                }
+            ],
+            p6: {
+                "odata.id": 78910,
+                e9: 900,
+                "e9@odata.type": "Edm.Int32"
+            }
+        };
+
+        var expected = {
+            __metadata: {
+                type: "Ns.EntityType",
+                properties: {
+                    p1: {
+                        type: "Ns.EntityType1"
+                    },
+                    p2: {
+                        type: "Ns.EntityType2"
+                    },
+                    p3: {
+                        type: null,
+                        navigationLinkUrl: "http://someUri/entitySet(1)/p3",
+                        associationLinkUrl: "http://someUri/entitySet(1)/$links/p3"
+                    },
+                    p4: {
+                        type: null,
+                        navigationLinkUrl: "http://someUri/entitySet(1)/p4",
+                        associationLinkUrl: "http://someUri/entitySet(1)/$links/p4"
+                    },
+                    p5: {
+                        type: null
+                    },
+                    p6: {
+                        type: null
+                    }
+                }
+            },
+            p1: {
+                __count: 50,
+                __next: "http://someUri/nextToken",
+                results: [
+            {
+                __metadata: {
+                    type: "Ns.EntityType1",
+                    properties: {
+                        e1: { type: null },
+                        e2: { type: null }
+                    }
+                },
+                e1: 100,
+                e2: 200
+            },
+            {
+                __metadata: {
+                    type: "Ns.EntityType1",
+                    properties: {
+                        e1: { type: null },
+                        e2: { type: null }
+                    }
+                },
+                e1: 110,
+                e2: 220
+            }
+          ]
+            },
+            p2: {
+                __metadata: {
+                    type: "Ns.EntityType2",
+                    properties: {
+                        e3: { type: null }
+                    }
+                },
+                e3: 300
+            },
+            p3: {
+                results: [
+            {
+                __metadata: {
+                    type: null,
+                    properties: {
+                        e4: { type: null },
+                        e5: { type: null }
+                    }
+                },
+                e4: 400,
+                e5: 500
+            },
+            {
+                __metadata: {
+                    type: null,
+                    properties: {
+                        e4: { type: null },
+                        e5: { type: null }
+                    }
+                },
+                e4: 440,
+                e5: 550
+            }
+            ]
+            },
+            p4: {
+                __metadata: {
+                    type: null,
+                    properties: {
+                        e6: { type: null }
+                    }
+                },
+                e6: 600
+            },
+            p5: {
+                results: [
+                {
+                    __metadata: {
+                        id: 12345,
+                        type: null,
+                        properties: {
+                            e7: { type: null },
+                            e8: { type: null }
+                        }
+                    },
+                    e7: 700,
+                    e8: 800
+                }
+            ]
+            },
+            p6: {
+                __metadata: {
+                    id: 78910,
+                    type: null,
+                    properties: {
+                        e9: { type: "Edm.Int32" }
+                    }
+                },
+                e9: 900
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light object with inlined navigation properties was read properly", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadStringPropertiesTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Ns",
+                    "entityType": [{
+                        "name": "EntityType",
+                        "property": [
+                                { "name": "p1", "type": "Edm.DateTime" },
+                                { "name": "p2", "type": "Edm.DateTimeOffset" },
+                                { "name": "p3", "type": "Edm.Time" }
+                            ]
+                    }]
+                }]
+            }
+        };
+
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/Ns.EntityType/@Element",
+            p1: "2000-01-02T03:04:05",
+            p2: "2000-01-02T03:04:05+01:00",
+            p3: "P0Y0M05DT12H30M5S",
+            p4: "hello world",
+            p5: "2001-01-02T03:04:05",
+            "p5@odata.type": "Edm.DateTime",
+            p6: "2001-01-02T03:04:05+01:00",
+            "p6@odata.type": "Edm.DateTimeOffset",
+            p7: "P0Y0M05DT12H30M10S",
+            "p7@odata.type": "Edm.Time"
+        };
+
+        var p2 = new Date("01/02/2000 02:04:05 GMT");
+        p2.__edmType = "Edm.DateTimeOffset";
+        p2.__offset = "+01:00";
+
+        var p6 = new Date("01/02/2001 02:04:05 GMT");
+        p2.__edmType = "Edm.DateTimeOffset";
+        p2.__offset = "+01:00";
+
+        var expected = {
+            __metadata: {
+                type: "Ns.EntityType",
+                properties: {
+                    p1: { type: "Edm.DateTime" },
+                    p2: { type: "Edm.DateTimeOffset" },
+                    p3: { type: "Edm.Time" },
+                    p4: { type: null },
+                    p5: { type: "Edm.DateTime" },
+                    p6: { type: "Edm.DateTimeOffset" },
+                    p7: { type: "Edm.Time" }
+                }
+            },
+            p1: new Date("01/02/2000 03:04:05 GMT"),
+            p3: { ms: 477005000, __edmType: "Edm.Time" },
+            p2: p2,
+            p4: "hello world",
+            p5: new Date("01/02/2001 03:04:05 GMT"),
+            p6: p6,
+            p7: { ms: 477010000, __edmType: "Edm.Time" }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light object with string properties was read properly", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadRecognizeDateLiteralsTest() {
+        var input = {
+            p1: "2000-01-02T03:04:05",
+            p2: "2000-01-02T03:04:05+01:00"
+        };
+
+        var p2 = new Date("01/02/2000 02:04:05 GMT");
+        p2.__edmType = "Edm.DateTimeOffset";
+        p2.__offset = "+01:00";
+
+        var expected = {
+            __metadata: {
+                properties: {
+                    p1: {
+                        type: null
+                    },
+                    p2: {
+                        type: null
+                    }
+                },
+                type: null
+            },
+            p1: new Date("01/02/2000 03:04:05 GMT"),
+            p2: p2
+        };
+
+        OData.jsonHandler.recognizeDates = true;
+        verifyReadJsonLightData(input, expected, "Json light datetime literals were recognized. ");
+
+        OData.jsonHandler.recognizeDates = false;
+
+        expected.p1 = input.p1;
+        expected.p2 = input.p2;
+
+        verifyReadJsonLightData(input, expected, "Json light datetime literals were ignored");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadEmptyFeedTest() {
+        var input = { "odata.metadata": "http://someUri#entitySet/Set", value: [] };
+        var expected = { results: [] };
+
+        verifyReadJsonLightData(input, expected, "Json light feed object was read properly");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadFeedTest() {
+        var input = {
+            "odata.metadata": "http://someUri#entitySet/Type",
+            value: [
+              { "odata.id": 12345 },
+              { "odata.id": 56789 }
+            ],
+            "odata.count": 50,
+            "odata.nextLink": "skipToken"
+        };
+
+        var expected = {
+            __count: 50,
+            __next: "http://someUri/skipToken",
+            results: [
+                {
+                    __metadata: {
+                        id: 12345,
+                        type: "Type"
+                    }
+                },
+                {
+                    __metadata: {
+                        id: 56789,
+                        type: "Type"
+                    }
+                }
+            ]
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light feed object was read properly");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadFeedUsingMetadataTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Test.Catalog",
+                    "entityContainer": [{
+                        "name": "TestCatalog",
+                        "isDefaultEntityContainer": "true",
+                        "entitySet": [
+                           { "name": "Titles", "entityType": "TestCatalog.Model.Title" }
+                        ]
+                    }
+                    ]
+                }
+                ]
+            }
+        };
+
+        var tests = {
+            "Unqualified entity set": {
+                input: { "odata.metadata": "http://someUri#Titles", value: [{}] },
+                expected: { results: [{ __metadata: { type: "TestCatalog.Model.Title"}}] }
+            },
+            "Qualified entity set": {
+                input: { "odata.metadata": "http://someUri#Test.Catalog.TestCatalog.Titles", value: [{}] },
+                expected: { results: [{ __metadata: { type: "TestCatalog.Model.Title"}}] }
+            },
+            "Type casted entity set": {
+                input: { "odata.metadata": "http://someUri#TestCatalog.Titles/TypeCast", value: [{}] },
+                expected: { results: [{ __metadata: { type: "TypeCast"}}] }
+            }
+        };
+
+        for (var name in tests) {
+            var test = tests[name];
+            verifyReadJsonLightData(test.input, test.expected, name + " - Json light feed was read properly.", testModel);
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadInferFeedAsObjectTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Test.Catalog",
+                    "entityContainer": [{
+                        "name": "TestCatalog",
+                        "isDefaultEntityContainer": "true",
+                        "entitySet": [
+                           { "name": "Titles", "entityType": "TestCatalog.Model.Title" }
+                        ]
+                    }
+                    ]
+                }
+                ]
+            }
+        };
+
+        var tests = {
+            "Feed as object": {
+                input: { "odata.metadata": "http://someUri#EntitySet", value: [] },
+                expected: {
+                    __metadata: {
+                        type: "EntitySet",
+                        properties: {
+                            value: { type: null }
+                        }
+                    },
+                    value: { __metadata: { type: null }, results: [] }
+                }
+            },
+            "Feed as feed using value": {
+                input: { "odata.metadata": "http://someUri#EntiySet", value: [{ "odata.id": 12345}] },
+                expected: { results: [{ __metadata: { type: null, id: 12345}}] }
+            },
+            "Feed as feed using metadata": {
+                input: { "odata.metadata": "http://someUri#Titles", value: [] },
+                expected: { results: [] }
+            },
+            "Collection of primitive": {
+                input: { "odata.metadata": "http://someUri#Collection(Edm.Int32)", value: [] },
+                expected: { __metadata: { type: "Collection(Edm.Int32)" }, results: [] }
+            },
+            "Collection of complex": {
+                input: { "odata.metadata": "http://someUri#Collection(My.Type)", value: [] },
+                expected: { __metadata: { type: "Collection(My.Type)" }, results: [] }
+            }
+        };
+
+        for (var name in tests) {
+            var test = tests[name];
+            var response = {
+                headers: {
+                    "Content-Type": "application/json;odata.metadata=full",
+                    DataServiceVersion: "4.0"
+                },
+                body: JSON.stringify(test.input)
+            };
+
+            OData.jsonHandler.read(response, { metadata: testModel });
+            djstest.assertAreEqualDeep(response.data, test.expected, name + " - Json light object was read properly ");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadEmptyLinksDocumentTest() {
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/$links/NavProp",
+            value: []
+        };
+
+        var expected = {
+            results: []
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light links document was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadLinksDocumentTest() {
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/$links/NavProp",
+            value: [
+                        { url: "Products(1)" },
+                        { url: "http://someUri/Products(2)" }
+                     ]
+        };
+
+        var expected = {
+            results: [
+                        { uri: "http://someUri/Products(1)" },
+                        { uri: "http://someUri/Products(2)" }
+                    ]
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light links document was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadSingleLinkDocumentTest() {
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/$links/NavProp/@Element",
+            url: "Products(1)"
+        };
+
+        var expected = {
+            uri: "http://someUri/Products(1)"
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light single link document was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadTopLevelPrimitiveProperty() {
+        var input = {
+            "odata.metadata": "http://someUri#Edm.GeometryPoint",
+            value: {
+                type: "Point",
+                coordinates: [1.0, 2.0],
+                crs: {
+                    type: name,
+                    properties: {
+                        name: "EPSG:4326"
+                    }
+                }
+            }
+        };
+
+        var expected = {
+            __metadata: {
+                type: "Edm.GeometryPoint"
+            },
+            value: {
+                type: "Point",
+                coordinates: [1.0, 2.0],
+                crs: {
+                    type: name,
+                    properties: {
+                        name: "EPSG:4326"
+                    }
+                }
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light top level primitive property was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadTopLevelComplexTypeTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Ns",
+                    "complexType": [{
+                        "name": "ComplexType",
+                        "property": [
+                            { "name": "value", "type": "Collection(Ns.ComplexType2)" }
+                        ]
+                    }]
+                }]
+            }
+        };
+
+        var input = {
+            "odata.metadata": "http://someUri#Ns.ComplexType",
+            value: [{
+                p1: 100,
+                p2: 200
+            }]
+        };
+
+        var expected = {
+            __metadata: {
+                type: "Ns.ComplexType",
+                properties: {
+                    value: {
+                        type: "Collection(Ns.ComplexType2)",
+                        elements: [
+                            {
+                                type: "Ns.ComplexType2",
+                                properties: {
+                                    p1: { type: null },
+                                    p2: { type: null }
+                                }
+                            }
+                        ]
+                    }
+                }
+            },
+            value: {
+                __metadata: { type: "Collection(Ns.ComplexType2)" },
+                results: [
+                    {
+                        __metadata: { type: "Ns.ComplexType2" },
+                        p1: 100,
+                        p2: 200
+                    }
+                ]
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light top level complex type property was read properly.", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadTopPrimitiveCollectionPropertyTest() {
+        var input = {
+            "odata.metadata": "http://someUri#Collection(Edm.GeometryPoint)",
+            value: [{
+                type: "Point",
+                coordinates: [1.0, 2.0],
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:4326"
+                    }
+                }
+            }]
+        };
+
+        var expected = {
+            __metadata: {
+                type: "Collection(Edm.GeometryPoint)"
+            },
+            results: [{
+                type: "Point",
+                coordinates: [1.0, 2.0],
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:4326"
+                    }
+                }
+            }]
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light top level primitive collection property was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadTopLevelComplexTypeCollectionTest() {
+        var input = {
+            "odata.metadata": "http://someUri#Collection(Ns.ComplexType2)",
+            value: [{
+                p1: 100,
+                p2: 200,
+                "p2@odata.type": "Edm.Int16"
+            }]
+        };
+
+        var expected = {
+            __metadata: {
+                type: "Collection(Ns.ComplexType2)",
+                elements: [
+                    {
+                        type: "Ns.ComplexType2",
+                        properties: {
+                            p1: { type: null },
+                            p2: { type: "Edm.Int16" }
+                        }
+                    }
+                ]
+            },
+            results: [
+                {
+                    __metadata: { type: "Ns.ComplexType2" },
+                    p1: 100,
+                    p2: 200
+                }
+             ]
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light top level complex type collection property was read properly.");
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadFeedAdvertisedActionsTest() {
+
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Ns",
+                    "entityContainer": [{
+                        "name": "EntityContainer",
+                        "isDefaultEntityContainer": "true",
+                        "entitySet": [
+                           { "name": "EntitySet", "entityType": "Ns.EntityType" }
+                        ]
+                    }
+                    ]
+                }
+                ]
+            }
+        };
+
+        input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet",
+            "#action1": {
+                target: "action1/item(0)",
+                title: "Action1"
+            },
+            "#action2": {
+                target: "action2/item(0)",
+                title: "Action2"
+            },
+            value: []
+        };
+
+        expected = {
+            __metadata: {
+                actions: [
+                    {
+                        metadata: "http://someUri/action1",
+                        target: "http://someUri/action1/item(0)",
+                        title: "Action1"
+                    },
+                   {
+                       metadata: "http://someUri/action2",
+                       target: "http://someUri/action2/item(0)",
+                       title: "Action2"
+                   }
+                ]
+            },
+            results: []
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light feed with advertised actions was read properly.", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightReadAdvertisedActionsAndFunctionsTest() {
+        var testModel = {
+            "version": "1.0",
+            "dataServices": {
+                "dataServiceVersion": "4.0",
+                "schema": [{
+                    "namespace": "Test.Catalog",
+                    "entityContainer": [{
+                        "name": "TestCatalog",
+                        "isDefaultEntityContainer": "true",
+                        "functionImport": [
+                            { "name": "function1", "isSideEffecting": "false" },
+                            { "name": "function2", "isSideEffecting": "false" },
+                            { "name": "action1", "isSideEffecting": "true" },
+                            { "name": "action2" }
+                         ]
+                    }, {
+                        "name": "OtherCatalog",
+                        "isDefaultEntityContainer": "false",
+                        "functionImport": [
+                            { "name": "function1", "isSideEffecting": "false" }
+                         ]
+                    }]
+                }]
+            }
+        };
+
+        input = {
+            "odata.metadata": "http://someUri/$metadata#EntitySet/@Element",
+            "#function2": [
+                {
+                    target: "function2/item(0)",
+                    title: "Function2 overload1"
+                },
+                {
+                    target: "function2/item(0)",
+                    title: "Function2 overload2"
+                }
+            ],
+            "#action1": {
+                target: "action1/item(0)",
+                title: "Action1"
+            },
+            "#action2": {
+                target: "action2/item(0)",
+                title: "Action2"
+            },
+            "#function1": {
+                target: "function1/item(0)",
+                title: "Function1"
+            },
+            "#Test.Catalog.OtherCatalog.function1": {
+                target: "Test.Catalog.OtherCatalog.function1/item(0)",
+                title: "Function1 in other catalog"
+            },
+            "#action3": [
+                {
+                    target: "action3/item(0)",
+                    title: "Unkown action overload1"
+                },
+                {
+                    target: "http://otherUri/action3/item(0)",
+                    title: "Unkown action overload2"
+                }
+            ]
+        };
+
+        expected = {
+            __metadata: {
+                type: null,
+                actions: [
+                   {
+                       metadata: "http://someUri/action1",
+                       target: "http://someUri/action1/item(0)",
+                       title: "Action1"
+                   },
+                   {
+                       metadata: "http://someUri/action2",
+                       target: "http://someUri/action2/item(0)",
+                       title: "Action2"
+                   },
+                   {
+                       metadata: "http://someUri/action3",
+                       target: "http://someUri/action3/item(0)",
+                       title: "Unkown action overload1"
+                   },
+                   {
+                       metadata: "http://someUri/action3",
+                       target: "http://otherUri/action3/item(0)",
+                       title: "Unkown action overload2"
+                   }
+                ],
+                functions: [
+                    {
+                        metadata: "http://someUri/function2",
+                        target: "http://someUri/function2/item(0)",
+                        title: "Function2 overload1"
+                    },
+                    {
+                        metadata: "http://someUri/function2",
+                        target: "http://someUri/function2/item(0)",
+                        title: "Function2 overload2"
+                    },
+                    {
+                        metadata: "http://someUri/function1",
+                        target: "http://someUri/function1/item(0)",
+                        title: "Function1"
+                    },
+                    {
+                        metadata: "http://someUri/Test.Catalog.OtherCatalog.function1",
+                        target: "http://someUri/Test.Catalog.OtherCatalog.function1/item(0)",
+                        title: "Function1 in other catalog"
+                    }
+                ]
+            }
+        };
+
+        verifyReadJsonLightData(input, expected, "Json light advertised actions and functions were read properly.", testModel);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightSerializeEntryMetadataTest() {
+        var data = {
+            __metadata: {
+                metadata: "http://someUri/$metadata#NS.Container/Set/@Element",
+                id: "http://someUri/Customers(1)",
+                etag: "etag-value",
+                self: "http://someUri/read/Customers(1)",
+                edit: "http://someUri/read/Customers(1)",
+                media_src: "http://someUri/Customers(1)/Image",
+                edit_media: "http://someUri/Customers(1)/$value",
+                media_etag: "stream-etag-value",
+                content_type: "image/jpg"
+            }
+        };
+
+        var expected = {
+            "odata.etag": "etag-value",
+            "odata.mediaReadLink": "http://someUri/Customers(1)/Image",
+            "odata.mediaEditLink": "http://someUri/Customers(1)/$value",
+            "odata.mediaETag": "stream-etag-value",
+            "odata.mediaContentType": "image/jpg"
+        };
+
+        verifySerializedJsonLightData(data, expected, " Json light entry metadata was serialized properly.");
+    });
+
+    djstest.addTest(function jsonLightSerializeCustomAnnotationsTest() {
+        var data = {
+            __metadata: {
+                id: "id"
+            },
+            "odata.id": "id",
+            "custom.annotation": "custom annotation value",
+            "p1@ns1.primitive": "primitive",
+            "p1@ns2.complex": { a1: 500 },
+            "p1@ns3.ns4.value": 600,
+            "p2@ns1.primitive": 400,
+            "custom.annotation": "custom annotation value"
+        };
+
+        var expected = {
+            "odata.id": "id",
+            "custom.annotation": "custom annotation value",
+            "p1@ns1.primitive": "primitive",
+            "p1@ns2.complex": { a1: 500 },
+            "p1@ns3.ns4.value": 600,
+            "p2@ns1.primitive": 400
+        };
+
+        verifySerializedJsonLightData(data, expected, " Json light custom annotations were serialized properly.");
+    });
+
+    djstest.addTest(function jsonLightSerializeEntryCollectionPropertiesTest() {
+        var data = {
+            __metadata: {
+                id: "id",
+                properties: {
+                    primitiveColArray: { type: "Collection(Edm.Int16)" },
+                    primitiveColObject: { type: "Collection(Edm.Int32)" }
+                }
+            },
+            primitiveColArray: [1, 2, 3, 4],
+            primitiveColObject: {
+                results: [5, 6, 7, 8]
+            },
+            complexColArray: [{ p1: 100 }, { p1: 200}],
+            complexColObject: {
+                results: [{ p1: 300 }, { p1: 400}]
+            }
+        };
+
+        var expected = {
+            "primitiveColArray@odata.type": "Collection(Edm.Int16)",
+            primitiveColArray: [1, 2, 3, 4],
+            "primitiveColObject@odata.type": "Collection(Edm.Int32)",
+            primitiveColObject: [5, 6, 7, 8],
+            complexColArray: [{ p1: 100 }, { p1: 200}],
+            complexColObject: [{ p1: 300 }, { p1: 400}]
+        };
+
+        verifySerializedJsonLightData(data, expected, " Json light entry collection properties were serialized properly.");
+    });
+
+    djstest.addTest(function jsonLightSerializeEntryDeferredPropertyTest() {
+        var data = {
+            __metadata: {
+                id: "id",
+                properties: {
+                    deferred: {
+                        associationLinkUrl: "http://target/$links"
+                    }
+                }
+            },
+            deferred: {
+                __deferred: { uri: "http://target" }
+            }
+        };
+
+        var expected = {
+            "deferred@odata.navigationLinkUrl": "http://target"
+        };
+
+        verifySerializedJsonLightData(data, expected, " Json light entry deferred property were serialized properly.");
+    });
+
+    djstest.addTest(function jsonLightSerializeEntryInlinePropertiesTest() {
+        var data = {
+            __metadata: {
+                id: "id"
+            },
+            inlineEntry: {
+                __metadata: { uri: "", properties: { p1: { type: "Edm.Int64"}} },
+                p1: "300"
+            },
+            inlineBoundEntry: {
+                __metadata: { uri: "http://entries(1)", properties: { p1: { type: "Edm.Int64"}} },
+                p1: "400"
+            },
+            inlineFeedArray: [
+                {
+                    __metadata: { uri: "http://entries(2)" }
+                },
+                {
+                    __metadata: { uri: "", properties: { p1: { type: "Edm.Int32"}} },
+                    p1: "600"
+                },
+                {
+                    __metadata: { uri: "http://entries(3)" }
+                }
+            ],
+            inlineFeedObject: {
+                __count: 50,
+                __next: "next link",
+                results: [
+                    { __metadata: { uri: "" }, p1: "900" }
+                ]
+            },
+            inlineEmptyFeedObject: {
+                results: []
+            }
+        };
+
+        var expected = {
+            inlineEntry: {
+                "p1@odata.type": "Edm.Int64",
+                p1: "300"
+            },
+            "inlineBoundEntry@odata.bind": "http://entries(1)",
+            inlineFeedArray: [{
+                "p1@odata.type": "Edm.Int32",
+                p1: "600"
+            }],
+            "inlineFeedArray@odata.bind": [
+                "http://entries(2)",
+                "http://entries(3)"
+            ],
+            inlineFeedObject: [{
+                p1: "900"
+            }],
+            inlineEmptyFeedObject: []
+        };
+
+        verifySerializedJsonLightData(data, expected, " Json light entry inline properties were serialized properly.");
+
+    });
+
+    djstest.addTest(function jsonLightSerializeEntryComplexPropertyTest() {
+        var data = {
+            __metadata: {
+                id: "id",
+                properties: {
+                    complexAsDeferredOnMetadata: {
+                        type: "complexAsDeferredOnMetadata.type"
+                    },
+                    complexAsCol: {
+                        type: "complexAsCol.type"
+                    }
+                }
+            },
+            complexAsDeferred: {
+                __metadata: {
+                    type: "complexAsDeferred.type"
+                },
+                __deferred: { uri: "http://uri" }
+            },
+            complexAsCol: {
+                results: [1, 2, 3, 4]
+            },
+            complexAsNamedStream: {
+                __metadata: {
+                    type: "complexAsNamedStream"
+                },
+                __mediaresource: {
+                    content_type: "content type",
+                    media_src: "http://source"
+                }
+            },
+            complexAsDeferredOnMetadata: {
+                __deferred: { uri: "http://uri2" }
+            }
+        };
+
+        var expected = {
+            complexAsDeferred: {
+                "odata.type": "complexAsDeferred.type",
+                __deferred: { uri: "http://uri" }
+            },
+            complexAsCol: {
+                "odata.type": "complexAsCol.type",
+                results: [1, 2, 3, 4]
+            },
+            complexAsNamedStream: {
+                "odata.type": "complexAsNamedStream",
+                __mediaresource: {
+                    content_type: "content type",
+                    media_src: "http://source"
+                }
+            },
+            complexAsDeferredOnMetadata: {
+                "odata.type": "complexAsDeferredOnMetadata.type",
+                __deferred: { uri: "http://uri2" }
+            }
+        };
+
+        verifySerializedJsonLightData(data, expected, " Json light entry complex properties were serialized properly.");
+    });
+
+    djstest.addTest(function jsonLightSerializeEntryComplexPropertyMetadataTest() {
+        var data = {
+            __metadata: {
+                id: "id",
+                properties: {
+                    complex: {
+                        type: "this should be overriden",
+                        properties: {
+                            nested1: {
+                                type: "types.complex.nested1",
+                                properties: {
+                                    nested2: {
+                                        type: "types.complex.nested1.nested2"
+                                    }
+                                }
+                            },
+                            c1: {
+                                type: "Edm.Int64"
+                            }
+                        }
+                    }
+                }
+            },
+            complex: {
+                __metadata: {
+                    type: "types.complex"
+                },
+                c1: "500",
+                c2: "b",
+                nested1: {
+                    nested2: {
+                    }
+                }
+            }
+        };
+
+        var expected = {
+            complex: {
+                "odata.type": "types.complex",
+                "c1@odata.type": "Edm.Int64",
+                c1: "500",
+                c2: "b",
+                nested1: {
+                    "odata.type": "types.complex.nested1",
+                    nested2: {
+                        "odata.type": "types.complex.nested1.nested2"
+                    }
+                }
+            }
+        };
+
+        verifySerializedJsonLightData(data, expected, " Json light entry complex property was serialized properly.");
+    });
+
+    djstest.addTest(function jsonLightSerializeLinksDocumentTest() {
+        var tests = {
+            "Empty uri string": {
+                i: { uri: "" },
+                e: { url: "" }
+            },
+            "Null uri string": {
+                i: { uri: null },
+                e: { url: null }
+            },
+            "Undefined uri string": {
+                i: { uri: undefined },
+                e: {}
+            },
+            "Uri": {
+                i: { uri: "http://somUri/EntitySet(1)" },
+                e: { url: "http://somUri/EntitySet(1)" }
+            }
+        };
+
+        for (var name in tests) {
+            verifySerializedJsonLightData(tests[name].i, tests[name].e, name + " - Json light links documents whas serialized properly.", "http://someUri/set(3)/$links/navprop");
+        }
+    });
+
+    djstest.addTest(function jsonLightComputeLinksWithSingleKey() {
+        var model = getSampleModel();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            Id: "MyId",
+            P1: 42
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": { "Id": { "type": "Edm.String" }, "P1": { "type": "Edm.Int32"} },
+                "id": "http://someUri/EntityTypes('MyId')",
+                "edit": "http://someUri/EntityTypes('MyId')",
+                "uri": "http://someUri/EntityTypes('MyId')",
+                "self": "http://someUri/EntityTypes('MyId')"
+
+            },
+            "Id": "MyId",
+            "P1": 42
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightComputeLinksWithMultipleKeys() {
+        var model = getSampleModelWithTwoKeys();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            Id: "MyId",
+            P1: 42
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": { "Id": { "type": "Edm.String" }, "P1": { "type": "Edm.Int32"} },
+                "id": "http://someUri/EntityTypes(Id='MyId',P1=42)",
+                "edit": "http://someUri/EntityTypes(Id='MyId',P1=42)",
+                "uri": "http://someUri/EntityTypes(Id='MyId',P1=42)",
+                "self": "http://someUri/EntityTypes(Id='MyId',P1=42)"
+            },
+            "Id": "MyId",
+            "P1": 42
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightComputeLinksWithNonComputedEditLink() {
+        var model = getSampleModel();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            "odata.editLink": "EntityTypes('notcomputed')",
+            Id: "MyId",
+            P1: 42
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": { "Id": { "type": "Edm.String" }, "P1": { "type": "Edm.Int32"} },
+                "id": "http://someUri/EntityTypes('notcomputed')",
+                "edit": "http://someUri/EntityTypes('notcomputed')",
+                "uri": "http://someUri/EntityTypes('notcomputed')",
+                "self": "http://someUri/EntityTypes('notcomputed')"
+            },
+            "Id": "MyId",
+            "P1": 42
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightComputeLinksWithSingleConcurrencyProperty() {
+        var model = getSampleModelWithOneConcurrencyProperty();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            Id: "MyId",
+            P1: 42
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": { "Id": { "type": "Edm.String" }, "P1": { "type": "Edm.Int32"} },
+                "id": "http://someUri/EntityTypes('MyId')",
+                "edit": "http://someUri/EntityTypes('MyId')",
+                "uri": "http://someUri/EntityTypes('MyId')",
+                "self": "http://someUri/EntityTypes('MyId')",
+                "etag": "W/\"'MyId'\""
+            },
+            "Id": "MyId",
+            "P1": 42
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightComputeLinksWithSingleBinaryConcurrencyProperty() {
+        var model = getSampleModelWithOneBinaryConcurrencyProperty();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            Id: "MyId",
+            P1: "AAAAAAAAB9E="
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": { "Id": { "type": "Edm.String" }, "P1": { "type": "Edm.Binary"} },
+                "id": "http://someUri/EntityTypes('MyId')",
+                "edit": "http://someUri/EntityTypes('MyId')",
+                "uri": "http://someUri/EntityTypes('MyId')",
+                "self": "http://someUri/EntityTypes('MyId')",
+                "etag": "W/\"X'00000000000007D1'\""
+            },
+            "Id": "MyId",
+            "P1": "AAAAAAAAB9E="
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightComputeLinkWithMutipleConcurrencyProperty() {
+        var model = getSampleModelWithMultipleConcurrencyProperties();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            Id: "MyId",
+            P1: 42
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": { "Id": { "type": "Edm.String" }, "P1": { "type": "Edm.Int32"} },
+                "id": "http://someUri/EntityTypes('MyId')",
+                "edit": "http://someUri/EntityTypes('MyId')",
+                "uri": "http://someUri/EntityTypes('MyId')",
+                "self": "http://someUri/EntityTypes('MyId')",
+                "etag": "W/\"'MyId',42\""
+            },
+            "Id": "MyId",
+            "P1": 42
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightComputeLinkWithNullConcurrencyProperty() {
+        var model = getSampleModelWithMultipleConcurrencyProperties();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            Id: "My Id'",
+            P1: null
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": { "Id": { "type": "Edm.String" }, "P1": { "type": "Edm.Int32"} },
+                "id": "http://someUri/EntityTypes('My%20Id''')",
+                "edit": "http://someUri/EntityTypes('My%20Id''')",
+                "uri": "http://someUri/EntityTypes('My%20Id''')",
+                "self": "http://someUri/EntityTypes('My%20Id''')",
+                "etag": "W/\"'My%20Id''',null\""
+            },
+            "Id": "My Id'",
+            "P1": null
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+    
+    djstest.addTest(function jsonLightWithDecimalValue() {
+        var model = getSampleModelWithDecimalProperty();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#EntityTypes/@Element",
+            Id: "5",
+            P1: "10.5"
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "NS.EntityType",
+                "properties": {
+                    "Id": {
+                        "type": "Edm.String"
+                    },
+                    "P1": {
+                        "type": "Edm.Decimal"
+                    },
+                },
+                "id": "http:\/\/someUri\/EntityTypes('5')",
+                "uri": "http:\/\/someUri\/EntityTypes('5')",
+                "edit": "http:\/\/someUri\/EntityTypes('5')",
+                "self": "http:\/\/someUri\/EntityTypes('5')"
+            },
+            "Id": "5",
+            "P1": "10.5"
+        };
+
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+    
+    djstest.addTest(function jsonLightNavigationPropertyAndAssociationUriShouldBeComputedTest() {
+        var model = getSampleModelWithNavPropertiesAndInheritedTypes();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#Products/@Element",
+            ID: 5,
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "ODataDemo.Product",
+                "properties": {
+                    "ID": {
+                        "type": "Edm.Int32"
+                    },
+                    "Category": {
+                        "type": "ODataDemo.Category",
+                        "associationLinkUrl": "http:\/\/someUri\/Products(5)\/$links\/Category",
+                    }
+                },
+                "id": "http:\/\/someUri\/Products(5)",
+                "uri": "http:\/\/someUri\/Products(5)",
+                "edit": "http:\/\/someUri\/Products(5)",
+                "self": "http:\/\/someUri\/Products(5)"
+            },
+            "ID": 5,
+            "Category": {
+                "__deferred": {
+                    "uri": "http:\/\/someUri\/Products(5)\/Category"
+                }
+            }
+        };
+
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+    
+    djstest.addTest(function jsonLightNullNavigationPropertyShouldNotBeComputedTest() {
+        var model = getSampleModelWithNavPropertiesAndInheritedTypes();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#Products/@Element",
+            ID: 5,
+            Category: null
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "ODataDemo.Product",
+                "properties": {
+                    "ID": {
+                        "type": "Edm.Int32"
+                    },
+                    "Category": {
+                        "type": "ODataDemo.Category",
+                        "associationLinkUrl": "http:\/\/someUri\/Products(5)\/$links\/Category",
+                         "navigationLinkUrl": "http:\/\/someUri\/Products(5)\/Category"
+                    }
+                },
+                "id": "http:\/\/someUri\/Products(5)",
+                "uri": "http:\/\/someUri\/Products(5)",
+                "edit": "http:\/\/someUri\/Products(5)",
+                "self": "http:\/\/someUri\/Products(5)"
+            },
+            "ID": 5,
+            "Category": null
+        };
+
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+    
+    djstest.addTest(function jsonLightUrisShouldBeComputedForExpandedEntities() {
+        var model = getSampleModelWithNavPropertiesAndInheritedTypes();
+        var input = {
+            "odata.metadata": "http://someUri/$metadata#Products/@Element",
+            ID: 5,
+            Category: {
+                ID: 1
+            }            
+        };
+
+        var expected = {
+            "__metadata": {
+                "type": "ODataDemo.Product",
+                "properties": {
+                    "ID": {
+                        "type": "Edm.Int32"
+                    },
+                    "Category": {
+                        "type": "ODataDemo.Category",
+                        "navigationLinkUrl": "http:\/\/someUri\/Products(5)\/Category",
+                        "associationLinkUrl": "http:\/\/someUri\/Products(5)\/$links\/Category"
+                    }
+                },
+                "id": "http:\/\/someUri\/Products(5)",
+                "uri": "http:\/\/someUri\/Products(5)",
+                "edit": "http:\/\/someUri\/Products(5)",
+                "self": "http:\/\/someUri\/Products(5)"
+            },
+            "ID": 5,
+            "Category": {
+                "__metadata": {
+                    "type": "ODataDemo.Category",
+                    "properties": {
+                        "ID": {
+                            "type": "Edm.Int32"
+                        },
+                        "Products": {
+                            "type": "ODataDemo.Product",
+                            "associationLinkUrl": "http:\/\/someUri\/Categories(1)\/$links\/Products"
+                        }
+                    },
+                    "id": "http:\/\/someUri\/Categories(1)",
+                    "uri": "http:\/\/someUri\/Categories(1)",
+                    "edit": "http:\/\/someUri\/Categories(1)",
+                    "self": "http:\/\/someUri\/Categories(1)"
+                },
+                "ID": 1,
+                "Products": {
+                    "__deferred": {
+                        "uri": "http:\/\/someUri\/Categories(1)\/Products"
+                    }
+                }
+            }
+        };
+
+        verifyReadJsonLightWithMinimalMetadata(input, expected, "Json light type annotations in payload are preferred over type information in the metadata document", model);
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonLightUrisShouldBeCo

<TRUNCATED>

[03/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-metadata-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-metadata-tests.js b/JSLib/tests/odata-metadata-tests.js
new file mode 100644
index 0000000..85e96ed
--- /dev/null
+++ b/JSLib/tests/odata-metadata-tests.js
@@ -0,0 +1,486 @@
+/// <reference path="../src/odata-atom.js" />
+/// <reference path="../src/odata-xml.js" />
+/// <reference path="../src/odata-metadata.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/djstest.js" />
+
+// odata-metadata-tests.js
+
+(function (window, undefined) {
+    djstest.addTest(function testMetadataHandler() {
+        // Test cases as result / model tuples.
+        var cases = [
+            { i: {}, e: undefined },
+            { i: { headers: { "Content-Type": "application/xml" }, body: '<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" />' },
+                e: { version: "4.0" }
+            }
+        ];
+
+        var i, len;
+        for (i = 0, len = cases.length; i < len; i++) {
+            var response = cases[i].i;
+            var testClient = { request: function (r, success, error) { success(response); } };
+            window.OData.read("foo", function (data) {
+                djstest.assertAreEqualDeep(data, cases[i].e, "handler result matches target");
+            }, function (err) {
+                djstest.fail(err.message);
+            }, window.OData.metadataHandler, testClient);
+        }
+
+        djstest.done();
+    });
+
+    // DATAJS INTERNAL START
+    djstest.addTest(function testScriptCase() {
+        // Test cases as input/result pairs.
+        var cases = [
+            { i: null, e: null },
+            { i: "", e: "" },
+            { i: "a", e: "a" },
+            { i: "A", e: "a" },
+            { i: "TestCase", e: "testCase" },
+            { i: "123abc", e: "123abc" },
+            { i: "ITEM", e: "ITEM" }
+        ];
+
+        var i, len;
+        for (i = 0, len = cases.length; i < len; i++) {
+            djstest.assertAreEqual(window.OData.scriptCase(cases[i].i), cases[i].e, "processed input matches expected value");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testGetChildSchema() {
+        // Test cases as input parent / input element / result tuples.
+        var schema = window.OData.schema;
+        var cases = [
+            { ip: schema.elements.EntityType, ie: "Property", e: { isArray: true, propertyName: "property"} },
+            { ip: schema.elements.EntityType, ie: "Key", e: { isArray: true, propertyName: "key"} },
+            { ip: schema.elements.EntitySet, ie: "SomethingElse", e: null },
+            { ip: schema.elements.Property, ie: "Name", e: null} // this is an attribute, not an element, thus it's no found
+        ];
+
+        var i, len;
+        for (i = 0, len = cases.length; i < len; i++) {
+            var result = window.OData.getChildSchema(cases[i].ip, cases[i].ie);
+            djstest.assertAreEqualDeep(result, cases[i].e, "getChildSchema matches target");
+        }
+
+        djstest.done();
+    });
+
+    var testFullCsdl = '' +
+        '<?xml version="1.0" encoding="utf-8"?>\r\n' +
+        '<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">\r\n' +
+        '  <edmx:DataServices xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" m:MaxDataServiceVersion="4.0" m:DataServiceVersion="4.0">\r\n' +
+        '    <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="TestCatalog.Model">\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" />\r\n' +
+        '        <NavigationProperty Name="Titles" Type="Collection(TestCatalog.Model.Title)" Partner="Series" />\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" />\r\n' +
+        '        <NavigationProperty Name="Titles" Type="Collection(TestCatalog.Model.Title)" Partner="Languages" />\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" />\r\n' +
+        '        <NavigationProperty Name="Awards" Type="Collection(TestCatalog.Model.TitleAward)" Partner="Person"/>\r\n' +
+        '        <NavigationProperty Name="TitlesActedIn" Type="Collection(TestCatalog.Model.Title)" Partner="Cast"/>\r\n' +
+        '        <NavigationProperty Name="TitlesDirected" Type="Collection(TestCatalog.Model.Title)" Partner="Directors"/>\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" />\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" Type="TestCatalog.Model.Title" Partner="AudioFormats" >\r\n' +
+        '            <ReferentialConstraint Property="TitleId" ReferencedProperty="Id" />' +
+        '        </NavigationProperty>' +
+        '      </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" />\r\n' +
+        '        <Property Name="Category" Type="Edm.String" Nullable="false" MaxLength="60" Unicode="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" Type="TestCatalog.Model.Title" Partner="Awards"/>\r\n' +
+        '        <NavigationProperty Name="Person" Type="TestCatalog.Model.Person" Partner="Awards"/>\r\n' +
+        '      </EntityType>\r\n' +
+        '      <EntityType Name="Title" HasStream="true">\r\n' +
+        '        <Key><PropertyRef Name="Id" /></Key>\r\n' +
+        '        <Property Name="Id" Type="Edm.String" Nullable="false" MaxLength="30" />\r\n' +
+        '        <Property Name="Synopsis" Type="Edm.String" Nullable="true" MaxLength="Max" Unicode="false" />\r\n' +
+        '        <Property Name="ShortSynopsis" Type="Edm.String" Nullable="true" MaxLength="Max" Unicode="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" />\r\n' +
+        '        <Property Name="Runtime" Type="Edm.Int32" Nullable="true" />\r\n' +
+        '        <Property Name="Rating" Type="Edm.String" Nullable="true" MaxLength="10" Unicode="false" />\r\n' +
+        '        <Property Name="DateModified" Type="Edm.DateTime" Nullable="false" />\r\n' +
+        '        <Property Name="Type" Type="Edm.String" Nullable="false" MaxLength="8" Unicode="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" />\r\n' +
+        '        <Property Name="Name" Type="Edm.String" Nullable="false" MaxLength="200" Unicode="false" />\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" />\r\n' +
+        '        <Property Name="WebsiteUrl" Type="Edm.String" Nullable="true" />\r\n' +
+        '        <Property Name="TestApiId" Type="Edm.String" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="AudioFormats" Type="Collection(TestCatalog.Model.TitleAudioFormat)" Partner="Title" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="Awards" Type="Collection(TestCatalog.Model.TitleAward)" Partner="Title" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="Disc" Type="Collection(TestCatalog.Model.Title)" />\r\n' +
+        '        <NavigationProperty Name="Movie" Type="Collection(TestCatalog.Model.Title)" />\r\n' +
+        '        <NavigationProperty Name="Season" Type="Collection(TestCatalog.Model.Title)" />\r\n' +
+        '        <NavigationProperty Name="Series" Type="Collection(TestCatalog.Model.Title)" />\r\n' +
+        '        <NavigationProperty Name="ScreenFormats" Type="Collection(TestCatalog.Model.TitleScreenFormat)" Partner="Title" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="Cast" Type="Collection(TestCatalog.Model.Person)" Partner="TitlesActedIn" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="Languages" Type="Collection(TestCatalog.Model.Language)" Partner="Titles" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="Directors" Type="Collection(TestCatalog.Model.Person)" Partner="TitlesDirected" Nullable="false" />\r\n' +
+        '        <NavigationProperty Name="Genres" Type="Collection(TestCatalog.Model.Genre)" Partner="Titles" Nullable="false" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <ComplexType Name="BoxArt">\r\n' +
+        '        <Property Name="SmallUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="false" />\r\n' +
+        '        <Property Name="MediumUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="false" />\r\n' +
+        '        <Property Name="LargeUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="false" />\r\n' +
+        '        <Property Name="HighDefinitionUrl" Type="Edm.String" Nullable="true" MaxLength="80" Unicode="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" />\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" />\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" />\r\n' +
+        '        <Property Name="DeliveryFormat" Type="Edm.String" Nullable="false" MaxLength="10" Unicode="false" />\r\n' +
+        '        <Property Name="Format" Type="Edm.String" Nullable="false" MaxLength="30" Unicode="false" />\r\n' +
+        '        <NavigationProperty Name="Title" Type="TestCatalog.Model.Title" Partner="ScreenFormats" >\r\n' +
+        '            <ReferentialConstraint Property="TitleId" ReferencedProperty="Id" />' +
+        '        </NavigationProperty>' +
+        '      </EntityType>\r\n' +
+        '      <Function Name="ProductsByRating">' +
+        '        <ReturnType Type="Collection(TestCatalog.Model.Title)" />\r\n' +
+        '      </Function>\r\n' +
+        '    </Schema>\r\n' +
+        '    <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Test.Catalog">\r\n' +
+        '      <EntityContainer Name="TestCatalog" >\r\n' +
+        '        <FunctionImport Name="Movies" EntitySet="Titles" Function="estCatalog.Model.GetTitles" />\r\n' +
+        '        <FunctionImport Name="Series" EntitySet="Titles" Function="estCatalog.Model.GetTitles" />\r\n' +
+        '        <FunctionImport Name="Seasons" EntitySet="Titles" Function="estCatalog.Model.GetTitles" />\r\n' +
+        '        <FunctionImport Name="Discs" EntitySet="Titles" Function="estCatalog.Model.GetTitles" />\r\n' +
+        '        <FunctionImport Name="Episodes" EntitySet="Titles" Function="estCatalog.Model.GetTitles" />\r\n' +
+        '        <EntitySet Name="Genres" EntityType="TestCatalog.Model.Genre" >\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Titles" />\r\n' +
+        '        </EntitySet>\r\n' +
+        '        <EntitySet Name="Languages" EntityType="TestCatalog.Model.Language" >\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Languages" />\r\n' +
+        '        </EntitySet>\r\n' +
+        '        <EntitySet Name="People" EntityType="TestCatalog.Model.Person" >\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Cast" />\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Directors" />\r\n' +
+        '        </EntitySet>' +
+        '        <EntitySet Name="TitleAudioFormats" EntityType="TestCatalog.Model.TitleAudioFormat" >\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="AudioFormats" />\r\n' +
+        '        </EntitySet>\r\n' +
+        '        <EntitySet Name="TitleAwards" EntityType="TestCatalog.Model.TitleAward" >\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Awards" />\r\n' +
+        '        </EntitySet>\r\n' +
+        '        <EntitySet Name="Titles" EntityType="TestCatalog.Model.Title" >\r\n' +
+        '            <NavigationPropertyBinding Target="TitleAudioFormats" Path="Title" />\r\n' +
+        '            <NavigationPropertyBinding Target="TitleAwards" Path="Title" />\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Disc" />\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Movie" />\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Season" />\r\n' +
+        '            <NavigationPropertyBinding Target="Titles" Path="Series" />\r\n' +
+        '            <NavigationPropertyBinding Target="TitleScreenFormats" Path="ScreenFormats" />\r\n' +
+        '            <NavigationPropertyBinding Target="People" Path="TitlesActedIn" />\r\n' +
+        '            <NavigationPropertyBinding Target="Languages" Path="Titles" />\r\n' +
+        '            <NavigationPropertyBinding Target="People" Path="TitlesDirected" />\r\n' +
+        '            <NavigationPropertyBinding Target="Genres" Path="Genres" />\r\n' +
+        '        </EntitySet>\r\n' +
+        '        <EntitySet Name="TitleScreenFormats" EntityType="TestCatalog.Model.TitleScreenFormat" >\r\n' +
+        '            <NavigationPropertyBinding Target="TitleScreenFormats" Path="Title" />\r\n' +
+        '        </EntitySet>\r\n' +
+        '      </EntityContainer>\r\n' +
+        '    </Schema>\r\n' +
+        '  </edmx:DataServices>\r\n' +
+        '</edmx:Edmx>\r\n' +
+        '';
+
+    var testFullMetadataResult = {
+        "version": "4.0",
+        "dataServices": {
+            "dataServiceVersion": "4.0",
+            "maxDataServiceVersion": "4.0",
+            "schema": [{
+                "namespace": "TestCatalog.Model",
+                "entityType": [{
+                    "name": "Genre",
+                    "key": [{ "propertyRef": [{ "name": "Name"}] }],
+                    "property": [{ "name": "Name", "type": "Edm.String", "nullable": "false", "maxLength": "50", "unicode": "false"}],
+                    "navigationProperty": [{ "name": "Titles", "partner": "Series", "type": "Collection(TestCatalog.Model.Title)"}]
+                }, {
+                    "name": "Language",
+                    "key": [{ "propertyRef": [{ "name": "Name"}] }],
+                    "property": [{ "name": "Name", "type": "Edm.String", "nullable": "false", "maxLength": "80", "unicode": "false"}],
+                    "navigationProperty": [{ "name": "Titles", "partner": "Languages", "type": "Collection(TestCatalog.Model.Title)"}]
+                }, {
+                    "name": "Person",
+                    "key": [{ "propertyRef": [{ "name": "Id"}] }],
+                    "property": [
+                        { "name": "Id", "type": "Edm.Int32", "nullable": "false" },
+                        { "name": "Name", "type": "Edm.String", "nullable": "false", "maxLength": "80", "unicode": "true" }
+                    ],
+                    "navigationProperty": [
+                        { "name": "Awards", "partner": "Person", "type": "Collection(TestCatalog.Model.TitleAward)" },
+                        { "name": "TitlesActedIn", "partner": "Cast", "type": "Collection(TestCatalog.Model.Title)" },
+                        { "name": "TitlesDirected", "partner": "Directors", "type": "Collection(TestCatalog.Model.Title)" }
+                    ]
+                }, {
+                    "name": "TitleAudioFormat",
+                    "key": [{ "propertyRef": [{ "name": "TitleId" }, { "name": "DeliveryFormat" }, { "name": "Language" }, { "name": "Format"}] }],
+                    "property": [
+                        { "name": "TitleId", "type": "Edm.String", "nullable": "false", "maxLength": "30" },
+                        { "name": "DeliveryFormat", "type": "Edm.String", "nullable": "false", "maxLength": "10", "unicode": "false" },
+                        { "name": "Language", "type": "Edm.String", "nullable": "false", "maxLength": "30", "unicode": "false" },
+                        { "name": "Format", "type": "Edm.String", "nullable": "false", "maxLength": "30", "unicode": "false" }
+                    ],
+                    "navigationProperty": [{ "name": "Title", "partner": "AudioFormats", "referentialConstraint": [{"property": "TitleId", "referencedProperty": "Id"}], "type": "TestCatalog.Model.Title" }]
+                }, {
+                    "name": "TitleAward",
+                    "key": [{ "propertyRef": [{ "name": "Id"}] }],
+                    "property": [
+                        { "name": "Id", "type": "Edm.Guid", "nullable": "false" },
+                        { "name": "Type", "type": "Edm.String", "nullable": "false", "maxLength": "30", "unicode": "false" },
+                        { "name": "Category", "type": "Edm.String", "nullable": "false", "maxLength": "60", "unicode": "false" },
+                        { "name": "Year", "type": "Edm.Int32", "nullable": "true" }, { "name": "Won", "type": "Edm.Boolean", "nullable": "false" }
+                    ],
+                    "navigationProperty": [
+                        { "name": "Title", "partner": "Awards", "type": "TestCatalog.Model.Title" },
+                        { "name": "Person", "partner": "Awards", "type": "TestCatalog.Model.Person" }
+                    ]
+                }, {
+                    "name": "Title",
+                    "hasStream": "true",
+                    "key": [{ "propertyRef": [{ "name": "Id"}] }],
+                    "property": [
+                        { "name": "Id", "type": "Edm.String", "nullable": "false", "maxLength": "30" },
+                        { "name": "Synopsis", "type": "Edm.String", "nullable": "true", "maxLength": "Max", "unicode": "false" },
+                        { "name": "ShortSynopsis", "type": "Edm.String", "nullable": "true", "maxLength": "Max", "unicode": "false" },
+                        { "name": "AverageRating", "type": "Edm.Double", "nullable": "true" }, { "name": "ReleaseYear", "type": "Edm.Int32", "nullable": "true" },
+                        { "name": "Url", "type": "Edm.String", "nullable": "true", "maxLength": "200", "unicode": "false" },
+                        { "name": "Runtime", "type": "Edm.Int32", "nullable": "true" },
+                        { "name": "Rating", "type": "Edm.String", "nullable": "true", "maxLength": "10", "unicode": "false" },
+                        { "name": "DateModified", "type": "Edm.DateTime", "nullable": "false"},
+                        { "name": "Type", "type": "Edm.String", "nullable": "false", "maxLength": "8", "unicode": "false" },
+                        { "name": "BoxArt", "type": "TestCatalog.Model.BoxArt", "nullable": "false" },
+                        { "name": "ShortName", "type": "Edm.String", "nullable": "false", "maxLength": "200", "unicode": "false" },
+                        { "name": "Name", "type": "Edm.String", "nullable": "false", "maxLength": "200", "unicode": "false" },
+                        { "name": "Instant", "type": "TestCatalog.Model.InstantAvailability", "nullable": "false" },
+                        { "name": "Dvd", "type": "TestCatalog.Model.DeliveryFormatAvailability", "nullable": "false" },
+                        { "name": "BluRay", "type": "TestCatalog.Model.DeliveryFormatAvailability", "nullable": "false" },
+                        { "name": "TinyUrl", "type": "Edm.String", "nullable": "false" },
+                        { "name": "WebsiteUrl", "type": "Edm.String", "nullable": "true" },
+                        { "name": "TestApiId", "type": "Edm.String", "nullable": "false" }
+                    ],
+                    "navigationProperty": [
+                        { "name": "AudioFormats", "nullable": "false", "partner": "Title", "type": "Collection(TestCatalog.Model.TitleAudioFormat)" },
+                        { "name": "Awards", "nullable": "false", "partner": "Title", "type": "Collection(TestCatalog.Model.TitleAward)" },
+                        { "name": "Disc", "type": "Collection(TestCatalog.Model.Title)" },
+                        { "name": "Movie", "type": "Collection(TestCatalog.Model.Title)" },
+                        { "name": "Season", "type": "Collection(TestCatalog.Model.Title)" },
+                        { "name": "Series", "type": "Collection(TestCatalog.Model.Title)" },
+                        { "name": "ScreenFormats", "nullable": "false", "partner": "Title", "type": "Collection(TestCatalog.Model.TitleScreenFormat)" },
+                        { "name": "Cast", "nullable": "false", "partner": "TitlesActedIn", "type": "Collection(TestCatalog.Model.Person)" },
+                        { "name": "Languages", "nullable": "false", "partner": "Titles", "type": "Collection(TestCatalog.Model.Language)" },
+                        { "name": "Directors", "nullable": "false", "partner": "TitlesDirected", "type": "Collection(TestCatalog.Model.Person)" },
+                        { "name": "Genres", "nullable": "false", "partner": "Titles", "type": "Collection(TestCatalog.Model.Genre)" }
+                    ]
+                }, {
+                    "name": "TitleScreenFormat",
+                    "key": [{ "propertyRef": [{ "name": "TitleId" }, { "name": "DeliveryFormat" }, { "name": "Format"}]}],
+                    "property": [
+                        { "name": "TitleId", "type": "Edm.String", "nullable": "false", "maxLength": "30" },
+                        { "name": "DeliveryFormat", "type": "Edm.String", "nullable": "false", "maxLength": "10", "unicode": "false" },
+                        { "name": "Format", "type": "Edm.String", "nullable": "false", "maxLength": "30", "unicode": "false" }
+                    ],
+                    "navigationProperty": [{ "name": "Title", "partner": "ScreenFormats", "referentialConstraint": [{"property": "TitleId", "referencedProperty": "Id"}], "type": "TestCatalog.Model.Title" }]
+                }],
+                "complexType": [{
+                    "name": "BoxArt",
+                    "property": [
+                        { "name": "SmallUrl", "type": "Edm.String", "nullable": "true", "maxLength": "80", "unicode": "false" },
+                        { "name": "MediumUrl", "type": "Edm.String", "nullable": "true", "maxLength": "80", "unicode": "false" },
+                        { "name": "LargeUrl", "type": "Edm.String", "nullable": "true", "maxLength": "80", "unicode": "false" },
+                        { "name": "HighDefinitionUrl", "type": "Edm.String", "nullable": "true", "maxLength": "80", "unicode": "false" }
+                    ]
+                }, {
+                    "name": "InstantAvailability",
+                    "property": [
+                        { "name": "Available", "type": "Edm.Boolean", "nullable": "false" },
+                        { "name": "AvailableFrom", "type": "Edm.DateTime", "nullable": "true" },
+                        { "name": "AvailableTo", "type": "Edm.DateTime", "nullable": "true" },
+                        { "name": "HighDefinitionAvailable", "type": "Edm.Boolean", "nullable": "false" },
+                        { "name": "Runtime", "type": "Edm.Int32", "nullable": "true" },
+                        { "name": "Rating", "type": "Edm.String", "nullable": "true", "maxLength": "10", "unicode": "false" }
+                    ]
+                }, {
+                    "name": "DeliveryFormatAvailability",
+                    "property": [
+                        { "name": "Available", "type": "Edm.Boolean", "nullable": "false" },
+                        { "name": "AvailableFrom", "type": "Edm.DateTime", "nullable": "true" },
+                        { "name": "AvailableTo", "type": "Edm.DateTime", "nullable": "true" },
+                        { "name": "Runtime", "type": "Edm.Int32", "nullable": "true" },
+                        { "name": "Rating", "type": "Edm.String", "nullable": "true", "maxLength": "10", "unicode": "false" }
+                    ]
+                }],
+                "function": [
+                {
+                   "name": "ProductsByRating",
+                   "returnType": {"type": "Collection(TestCatalog.Model.Title)" }
+                }]
+            }, {
+                "namespace": "Test.Catalog",
+                "entityContainer": {
+                    "name": "TestCatalog",
+                    "functionImport": [
+                        { "entitySet": "Titles", "function": "estCatalog.Model.GetTitles", "name": "Movies"},
+                        { "entitySet": "Titles", "function": "estCatalog.Model.GetTitles", "name": "Series"},
+                        { "entitySet": "Titles", "function": "estCatalog.Model.GetTitles", "name": "Seasons" },
+                        { "entitySet": "Titles", "function": "estCatalog.Model.GetTitles", "name": "Discs" },
+                        { "entitySet": "Titles", "function": "estCatalog.Model.GetTitles", "name": "Episodes" }
+                    ], "entitySet": [
+                        { "name": "Genres", "entityType": "TestCatalog.Model.Genre", "navigationPropertyBinding": [{"path": "Titles", "target": "Titles"}] },
+                        { "name": "Languages", "entityType": "TestCatalog.Model.Language", "navigationPropertyBinding": [{ "path": "Languages", "target": "Titles"}] },
+                        { "name": "People", "entityType": "TestCatalog.Model.Person", "navigationPropertyBinding": [{ "path": "Cast", "target": "Titles" }, { "path": "Directors", "target": "Titles"}] },
+                        { "name": "TitleAudioFormats", "entityType": "TestCatalog.Model.TitleAudioFormat", "navigationPropertyBinding": [{ "path": "AudioFormats", "target": "Titles"}] },
+                        { "name": "TitleAwards", "entityType": "TestCatalog.Model.TitleAward", "navigationPropertyBinding": [{ "path": "Awards", "target": "Titles"}] },
+                        { "name": "Titles", "entityType": "TestCatalog.Model.Title", "navigationPropertyBinding": [{ "path": "Title", "target": "TitleAudioFormats" }, { "path": "Title", "target": "TitleAwards" }, { "path": "Disc", "target": "Titles" }, { "path": "Movie", "target": "Titles" }, { "path": "Season", "target": "Titles" }, { "path": "Series", "target": "Titles" }, { "path": "ScreenFormats", "target": "TitleScreenFormats" }, { "path": "TitlesActedIn", "target": "People" }, { "path": "Titles", "target": "Languages" }, { "path": "TitlesDirected", "target": "People" }, { "path": "Genres", "target": "Genres"}] },
+                        { "name": "TitleScreenFormats", "entityType": "TestCatalog.Model.TitleScreenFormat", "navigationPropertyBinding": [{ "path": "Title", "target": "TitleScreenFormats"}] }
+                    ]
+                }
+            }]
+        }
+    };
+
+    var testCsdlV4 = '' +
+    '<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">\r\n' +
+    '  <edmx:DataServices xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" m:MaxDataServiceVersion="4.0" m:DataServiceVersion="4.0">\r\n' +
+    '    <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="TestCatalog.Model"/>\r\n' +
+    '  </edmx:DataServices>\r\n' +
+    '</edmx:Edmx>';
+
+    var testMetadataV4 = {
+        "version": "4.0",
+        "dataServices": {
+            "maxDataServiceVersion": "4.0",
+            "dataServiceVersion": "4.0",
+            "schema": [{
+                "namespace": "TestCatalog.Model"
+            }]
+        }
+    };
+
+    djstest.addTest(function testParseConceptualModelElement() {
+        // Test cases as input XML text / result tuples.
+        var cases = [
+            { i: "<foo />", e: null },
+            { i: '<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" />', e: { version: "4.0"} },
+            { i: '<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx-invalid" />', e: null },
+            { i: testCsdlV4, e: testMetadataV4 },
+            { i: testFullCsdl, e: testFullMetadataResult }
+        ];
+
+        var i, len;
+        for (i = 0, len = cases.length; i < len; i++) {
+            var doc = window.datajs.xmlParse(cases[i].i);
+            var schema = window.OData.parseConceptualModelElement(doc.documentElement);
+            djstest.assertAreEqualDeep(schema, cases[i].e, "parseConceptualModelElement result matches target");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function metadataVocabularyTest() {
+        var testCsdl = '' +
+        '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n' +
+        '<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" >\r\n' +
+        '  <edmx:DataServices xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" m:MaxDataServiceVersion="4.0" m:DataServiceVersion="4.0">\r\n' +
+        '    <Schema Namespace="TestCatalog.Model" xmlns="http://docs.oasis-open.org/odata/ns/edm">\r\n' +
+        '          <Term Name="Rating" Type="Edm.Int32" />\r\n' +
+        '          <Term Name="CanEdit" Type="Edm.String" />\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" />\r\n' +
+        '      </EntityType></Schema></edmx:DataServices></edmx:Edmx>';
+
+
+        var doc = window.datajs.xmlParse(testCsdl);
+        var schema = window.OData.parseConceptualModelElement(doc.documentElement);
+
+        djstest.assertAreEqual(schema.dataServices.schema[0].term.length, 2, "schema.DataServices.Schema.Term.length === 2");
+        djstest.assertAreEqual(schema.dataServices.schema[0].term[0].name, "Rating", "schema.DataServices.Schema.Term[0].name === 'Rating'");
+        djstest.assertAreEqual(schema.dataServices.schema[0].term[1].name, "CanEdit", "schema.DataServices.Schema.Term[1].name === 'CanEdit'");
+        djstest.done();
+    });
+
+    djstest.addTest(function metadataAnnotationTest() {
+        var testCsdl = '' +
+        '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n' +
+        '<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" >\r\n' +
+        '  <edmx:DataServices xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" m:MaxDataServiceVersion="4.0" m:DataServiceVersion="4.0">\r\n' +
+        '    <Schema Namespace="TestCatalog.Model" xmlns="http://docs.oasis-open.org/odata/ns/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" />\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" />\r\n' +
+        '        <Property Name="Id" Type="Edm.Int32" />\r\n' +
+        '      </EntityType>\r\n' +
+        '      <Annotations Target="TestCatalog.Model.Genre/Name">\r\n' +
+        '        <Annotation String="Genre Name" Term="Org.OData.Display.V1.DisplayName"/>\r\n' +
+        '      </Annotations>\r\n' +
+        '      <Annotations Target="TestCatalog.Model.Language/Name">\r\n' +
+        '        <Annotation String="Language Name" Term="Org.OData.Display.V1.DisplayName"/>\r\n' +
+        '        <Annotation String="Language Name 2" Term="Org.OData.Display.V1.DisplayName 2"/>\r\n' +
+        '      </Annotations>\r\n' +
+        '    </Schema></edmx:DataServices></edmx:Edmx>';
+
+
+        var doc = window.datajs.xmlParse(testCsdl);
+        var schema = window.OData.parseConceptualModelElement(doc.documentElement);
+
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations.length, 2, "Annotations number");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[0].annotation.length, 1, "Annotation number");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[0].annotation[0].string, "Genre Name", "Annotation name");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[0].annotation[0].term, "Org.OData.Display.V1.DisplayName", "Annotation term");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[1].annotation.length, 2, "Annotation number");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[1].annotation[0].string, "Language Name", "Annotation name");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[1].annotation[0].term, "Org.OData.Display.V1.DisplayName", "Annotation term");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[1].annotation[1].string, "Language Name 2", "Annotation name");
+        djstest.assertAreEqual(schema.dataServices.schema[0].annotations[1].annotation[1].term, "Org.OData.Display.V1.DisplayName 2", "Annotation term");
+        djstest.done();
+    });
+
+    // DATAJS INTERNAL END
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-net-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-net-tests.js b/JSLib/tests/odata-net-tests.js
new file mode 100644
index 0000000..35cd6c2
--- /dev/null
+++ b/JSLib/tests/odata-net-tests.js
@@ -0,0 +1,286 @@
+/// <reference path="../src/odata-net.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/djstest.js" />
+/// <reference path="common/mockXMLHttpRequest.js" />
+
+// odata-net-tests.js
+(function (window, undefined) {
+    module("Unit");
+    djstest.addTest(function httpClientSendRequestTest() {
+        var tests = [
+            { url: "http://localhost/test1", response: { headers: {}, status: 200, body: "test"} },
+            { url: "http://localhost/test2", response: { headers: {}, status: 204, body: "test"} },
+            { url: "http://localhost/test3", response: { headers: {}, status: 299, body: "test"} },
+            { url: "http://localhost/test4", response: { headers: {}, status: 500, body: "error"} }
+        ];
+
+        djstest.assertsExpected(12);
+
+        var sentCount = 0;
+        MockXMLHttpRequest.onAfterSend = function () {
+            sentCount++;
+        };
+
+        var oldXmlHttpRequest = window.XMLHttpRequest;
+        var oldEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+        try {
+            window.XMLHttpRequest = MockXMLHttpRequest.XMLHttpRequest;
+            var i, len;
+            for (i = 0, len = tests.length; i < len; i++) {
+                MockXMLHttpRequest.addResponse(tests[i].url, tests[i].response);
+
+                OData.defaultHttpClient.enableJsonpCallback = false;
+                //Need a closure to capture the current test being executed. 
+                (function (test) {
+                    OData.defaultHttpClient.request(
+                    { requestUri: test.url, headers: {} },
+                    function (response) {
+                        djstest.assert(response.statusCode >= 200 & response.statusCode <= 299, "response status is in the success range");
+                        djstest.assertAreEqual(response.body, test.response.body, "response body is the expected one");
+                        djstest.assertAreEqualDeep(response.headers, [], "response headers are the expected ones");
+                    },
+                    function (error) {
+                        djstest.assert(error.response.statusCode > 299, "response status is in the error range");
+                        djstest.assertAreEqual(error.response.body, test.response.body, "response body is the expected one");
+                        djstest.assertAreEqualDeep(error.response.headers, [], "response headers are the expected ones");
+                    });
+                })(tests[i]);
+            }
+        }
+        finally {
+            //Cleanup and finish the test after all requests have been sent and processed. Poll every 50 ms
+            var timer = setInterval(function () {
+                if (sentCount === tests.length) {
+                    clearInterval(timer)
+                    OData.defaultHttpClient.enableJsonpCallback = oldEnableJsonpCallback;
+                    window.XMLHttpRequest = oldXmlHttpRequest;
+                    MockXMLHttpRequest.reset();
+                    djstest.done();
+                }
+            }, 50);
+        }
+    });
+
+    djstest.addTest(function httpClientRequestTimeoutTest() {
+        var oldXmlHttpRequest = window.XMLHttpRequest;
+        var testDone = false;
+
+        djstest.assertsExpected(1);
+
+        var oldEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+        try {
+            window.XMLHttpRequest = MockXMLHttpRequest.XMLHttpRequest;
+            OData.defaultHttpClient.enableJsonpCallback = false;
+
+            OData.defaultHttpClient.request(
+               { requestUri: "http://test1", timeoutMS: 10, headers: { MockTimeOut: true} },
+               function (response) {
+                   djstest.fail("success method was hit when not expected");
+                   testDone = true;
+               },
+               function (error) {
+                   djstest.assertAreEqual(error.message, "Request timed out", "error method executes and error is the expected one");
+                   testDone = true;
+               });
+        }
+        finally {
+            //Cleanup and finish the test after all requests have been sent and processed. Poll every 50 ms
+            var timer = setInterval(function () {
+                if (testDone) {
+                    clearInterval(timer)
+                    OData.defaultHttpClient.enableJsonpCallback = oldEnableJsonpCallback;
+                    window.XMLHttpRequest = oldXmlHttpRequest;
+                    MockXMLHttpRequest.reset();
+                    djstest.done();
+                }
+            }, 50);
+        }
+    });
+
+    djstest.addTest(function httpClientRequestAbortTest() {
+
+        var oldXmlHttpRequest = window.XMLHttpRequest;
+
+        djstest.assertsExpected(1);
+
+        var oldEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+        try {
+            window.XMLHttpRequest = MockXMLHttpRequest.XMLHttpRequest;
+            OData.defaultHttpClient.enableJsonpCallback = false;
+
+            var result = OData.defaultHttpClient.request(
+               { requestUri: "http://test1", headers: { MockNoOp: true} },
+               function (response) {
+                   djstest.fail("success method was hit when not expected");
+               },
+               function (error) {
+                   djstest.assertAreEqual(error.message, "Request aborted", "error method executes and error is the expected one");
+               });
+
+            result.abort();
+        }
+        finally {
+            OData.defaultHttpClient.enableJsonpCallback = oldEnableJsonpCallback;
+            window.XMLHttpRequest = oldXmlHttpRequest;
+            MockXMLHttpRequest.reset();
+            djstest.done();
+        }
+    });
+
+    djstest.addTest(function httpClientRequestAbortOnCompletedRequestTest() {
+
+        var oldXmlHttpRequest = window.XMLHttpRequest;
+        var testDone = false;
+
+        djstest.assertsExpected(1);
+
+        var oldEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+        try {
+            window.XMLHttpRequest = MockXMLHttpRequest.XMLHttpRequest;
+            OData.defaultHttpClient.enableJsonpCallback = false;
+
+            MockXMLHttpRequest.addResponse("http://test1", { headers: {}, status: 200, body: "test body" });
+
+            MockXMLHttpRequest.onAfterSend = function () {
+                result.abort();
+                testDone = true;
+            };
+
+            result = OData.defaultHttpClient.request(
+               { requestUri: "http://test1", headers: {} },
+               function (response) {
+                   djstest.pass("success method was hit");
+               },
+               function (error) {
+                   djstest.fail("success method was hit when not expected - [" + error.message + "]");
+               });
+        }
+        finally {
+            // Cleanup after test is done, poll eavery 50ms
+            var timer = setInterval(function () {
+                if (testDone) {
+                    clearInterval(timer);
+                    OData.defaultHttpClient.enableJsonpCallback = oldEnableJsonpCallback;
+                    window.XMLHttpRequest = oldXmlHttpRequest;
+                    MockXMLHttpRequest.reset();
+                    djstest.done();
+                }
+            }, 50);
+        }
+    });
+
+    djstest.addTest(function httpClientRequestSendsRequestCorrectlyTest() {
+        var tests = [
+            {
+                request: { requestUri: "http://test1", headers: {}, body: "test" },
+                expected: { headers: {}, url: "http://test1", method: "GET", body: "test", async: true, user: undefined, password: undefined }
+            },
+            {
+                request: { requestUri: "http://test2", headers: {}, method: "POST", body: "test" },
+                expected: { headers: {}, url: "http://test2", method: "POST", body: "test", async: true, user: undefined, password: undefined }
+            },
+            {
+                request: { requestUri: "http://test3", headers: { header1: "value1", header2: "value2" }, body: "test" },
+                expected: { headers: { header1: "value1", header2: "value2" }, url: "http://test3", method: "GET", body: "test", async: true, user: undefined, password: undefined }
+            }
+        ];
+
+        var oldXmlHttpRequest = window.XMLHttpRequest;
+        var oldEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+        try {
+            window.XMLHttpRequest = MockXMLHttpRequest.XMLHttpRequest;
+            OData.defaultHttpClient.enableJsonpCallback = false;
+            var i, len;
+            for (i = 0, len = tests.length; i < len; i++) {
+
+                MockXMLHttpRequest.addRequestVerifier(tests[i].request.requestUri, function (request) {
+                    djstest.assertAreEqualDeep(request, tests[i].expected, "request matches target");
+                });
+
+                OData.defaultHttpClient.request(
+                    tests[i].request,
+                    function (response) { });
+            }
+        }
+        finally {
+            // Restore original values.
+            OData.defaultHttpClient.enableJsonpCallback = oldEnableJsonpCallback;
+            window.XMLHttpRequest = oldXmlHttpRequest;
+        }
+        djstest.done();
+    });
+
+    // DATAJS INTERNAL START
+    djstest.addTest(function canUseJSONPTest() {
+        var tests = [
+            { pass: true, input: {} },
+            { pass: true, input: { method: "GET"} },
+            { pass: false, input: { method: "PUT"} },
+            { pass: false, input: { method: "get"} },
+            { pass: true, input: { accept: "*/*"} },
+            { pass: true, input: { accept: "application/json"} },
+            { pass: true, input: { accept: "text/javascript"} },
+            { pass: true, input: { accept: "application/javascript"} },
+            { pass: true, input: { accept: "application/xml"} },
+            { pass: true, input: { headers: { Accept: "application/xml"}} }
+        ];
+        for (var i = 0; i < tests.length; i++) {
+            var actual = OData.canUseJSONP(tests[i].input);
+            djstest.assert(actual === tests[i].pass, "test " + i + " didn't actually match pass (" + tests[i].pass + ")");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function isAbsoluteUrlTest() {
+        djstest.assert(OData.isAbsoluteUrl("http://something/"));
+        djstest.assert(OData.isAbsoluteUrl("http://malformed url/"));
+        djstest.assert(OData.isAbsoluteUrl("https://localhost/"));
+        djstest.assert(OData.isAbsoluteUrl("file://another-protocol/"));
+        djstest.assert(!OData.isAbsoluteUrl("/path"));
+        djstest.assert(!OData.isAbsoluteUrl("?query-string"));
+        djstest.assert(!OData.isAbsoluteUrl(""));
+        djstest.assert(!OData.isAbsoluteUrl("mailto:someone"));
+        djstest.done();
+    });
+
+    djstest.addTest(function isLocalUrlTest() {
+        var thisUrl = window.location.href;
+        var localUrls = [
+            "", ".", "/howdy.htm", "  ", "?queryparam",
+            thisUrl, thisUrl + "/foo", thisUrl + "?something-else"
+        ];
+        var remoteUrls = [
+            "http://www.microsoft.com/",
+            "https://www.microsoft.com/",
+            "https://" + window.location.host,
+            "https://" + window.location.hostname,
+        // 21 is FTP, so the test shouldn't collide
+            "http://" + window.location.hostname + ":21"
+        ];
+        var i, len;
+        for (i = 0, len = localUrls.length; i < len; i++) {
+            djstest.assert(OData.isLocalUrl(localUrls[i]), "is local: [" + localUrls[i] + "]");
+        }
+        for (i = 0, len = remoteUrls.length; i < len; i++) {
+            djstest.assert(!OData.isLocalUrl(remoteUrls[i]), "is not local: [" + remoteUrls[i] + "]");
+        }
+        djstest.done();
+    });
+
+    // DATAJS INTERNAL END
+
+    djstest.addTest(function userPasswordTest() {
+        OData.request({
+            requestUri: "./endpoints/FoodStoreDataServiceV4.svc/UserNameAndPassword",
+            user: "the-user",
+            password: "the-password"
+        }, function (data) {
+            djstest.assertAreEqualDeep(data.value, "Basic dGhlLXVzZXI6dGhlLXBhc3N3b3Jk", "response matches");
+            djstest.done();
+        }, function (err) {
+            djstest.fail("error: " + err.message);
+            djstest.done();
+        });
+    });
+
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-perf-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-perf-tests.html b/JSLib/tests/odata-perf-tests.html
new file mode 100644
index 0000000..bdcbfb5
--- /dev/null
+++ b/JSLib/tests/odata-perf-tests.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>OData perf 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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="common/Instrument.js"></script>
+
+    <script type="text/javascript" src="odata-perf-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">OData perf tests</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-perf-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-perf-tests.js b/JSLib/tests/odata-perf-tests.js
new file mode 100644
index 0000000..f57bcc0
--- /dev/null
+++ b/JSLib/tests/odata-perf-tests.js
@@ -0,0 +1,229 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/Instrument.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    var timedHttpClient = {
+        duration: 0,
+        provider: OData.defaultHttpClient,
+        request: function (request, success, error) {
+            var startTime = new Date();
+            return timedHttpClient.provider.request(request, function () {
+                timedHttpClient.duration = new Date() - startTime;
+                success.apply(this, arguments);
+            }, error);
+        }
+    };
+
+    var largeCollectionService = "./endpoints/LargeCollectionService.svc/";
+
+    // null "service" indicates the feed is read-only
+    var feeds = [
+        // will add atom format test after enabling atom scenario
+        { service: largeCollectionService, uri: largeCollectionService + "Customers", mimeType: "application/json;odata.metadata=minimal" },
+        { service: largeCollectionService, uri: largeCollectionService + "Customers", mimeType: "application/json;odata.metadata=full" },
+        { service: largeCollectionService, uri: largeCollectionService + "Customers", mimeType: "application/json;odata.metadata=none" },
+        { service: largeCollectionService, uri: largeCollectionService + "Suppliers", mimeType: "application/json" },
+        { service: null, uri: "http://odata.netflix.com/Catalog/Titles", mimeType: "application/json" }
+    ];
+
+    module("Performance", {
+        setup: function () {
+            djstest.wait(function (done) {
+                $.post(largeCollectionService + "ResetData", done);
+            });
+        },
+        teardown: function () {
+            OData.defaultHttpClient = timedHttpClient.provider;
+        }
+    });
+
+    OData.defaultHttpClient.enableJsonpCallback = true;
+    $.each(feeds, function (_, feed) {
+        $.each([5, 500], function (_, items) {
+            var params = $.extend({}, feed, { items: items, readUri: feed.uri + "?$top=" + items });
+            djstest.addTest(function readPerfTest(params) {
+                var measureRead = function (metadata) {
+                    var startTime = new Date();
+                    OData.read({ requestUri: params.readUri, headers: { Accept: params.mimeType }, enableJsonpCallback: true }, function () {
+                        var duration = new Date() - startTime - timedHttpClient.duration;
+                        djstest.pass("Duration: " + duration + " ms (Network: " + timedHttpClient.duration + " ms)");
+                        djstest.done();
+                    }, unexpectedErrorHandler, undefined, undefined, metadata);
+                };
+
+                OData.defaultHttpClient = timedHttpClient;
+                djstest.assertsExpected(1);
+                if (params.metadata) {
+                    OData.read(params.service + "$metadata", measureRead, unexpectedErrorHandler, OData.metadataHandler);
+                } else {
+                    measureRead();
+                }
+            }, "Time to read (once) " + params.readUri + " with " + params.mimeType, params);
+
+            djstest.addTest(function readParallelMemoryTest(params) {
+                var total = 10;
+                var count = 0;
+                var measureRead = function (metadata) {
+                    Instrument.getBrowserMemorySize(function (memoryBefore) {
+                        for (var i = 0; i < total; i++) {
+                            OData.read({ requestUri: params.readUri, headers: { Accept: params.mimeType }, enableJsonpCallback: true }, function (_, response) {
+                                count++;
+                                if (count >= total) {
+                                    Instrument.getBrowserMemorySize(function (memoryAfter) {
+                                        var memory = memoryAfter - memoryBefore;
+                                        djstest.pass("Memory: " + memory + " bytes (Network: " + response.headers["Content-Length"] + " bytes)");
+                                        djstest.done();
+                                    });
+                                }
+                            }, unexpectedErrorHandler, undefined, undefined, metadata);
+                        }
+                    });
+                };
+
+                djstest.assertsExpected(1);
+                if (params.metadata) {
+                    OData.read(params.service + "$metadata", measureRead, unexpectedErrorHandler, OData.metadataHandler);
+                } else {
+                    measureRead();
+                }
+            }, "Memory to read (x10 parallel) " + params.readUri + " with " + params.mimeType, params, 300000);
+
+            djstest.addTest(function readSerialMemoryTest(params) {
+                var total = 10;
+                var count = 0;
+                var measureRead = function (metadata) {
+                    Instrument.getBrowserMemorySize(function (memoryBefore) {
+                        var makeRequest = function () {
+                            OData.read({ requestUri: params.readUri, headers: { Accept: params.mimeType }, enableJsonpCallback: true }, function (_, response) {
+                                count++;
+                                if (count < total) {
+                                    setTimeout(makeRequest, 0);
+                                } else {
+                                    Instrument.getBrowserMemorySize(function (memoryAfter) {
+                                        var memory = memoryAfter - memoryBefore;
+                                        djstest.pass("Memory: " + memory + " bytes (Network: " + response.headers["Content-Length"] + " bytes)");
+                                        djstest.done();
+                                    });
+                                }
+                            }, unexpectedErrorHandler, undefined, undefined, metadata);
+                        };
+
+                        makeRequest();
+                    });
+                };
+
+                djstest.assertsExpected(1);
+                if (params.metadata) {
+                    OData.read(params.service + "$metadata", measureRead, unexpectedErrorHandler, OData.metadataHandler);
+                } else {
+                    measureRead();
+                }
+            }, "Memory to read (x10 serial) " + params.readUri + " with " + params.mimeType, params, 300000);
+        });
+
+        if (feed.service) {
+            var params = $.extend({}, feed, {
+                request: {
+                    requestUri: feed.uri,
+                    method: "POST",
+                    headers: { "Content-Type": feed.mimeType, Accept: feed.mimeType },
+                    data: {
+                        ID: -1,
+                        Name: "New Entity"
+                    }
+                }
+            });
+
+            djstest.addTest(function postPerfTest(params) {
+                var measurePost = function (metadata) {
+                    var startTime = new Date();
+                    OData.request(params.request, function () {
+                        var duration = new Date() - startTime - timedHttpClient.duration;
+                        djstest.pass("Duration: " + duration + " ms (Network: " + timedHttpClient.duration + " ms)");
+                        djstest.done();
+                    }, unexpectedErrorHandler, undefined, undefined, metadata);
+                };
+
+                OData.defaultHttpClient = timedHttpClient;
+                djstest.assertsExpected(1);
+
+                if (params.metadata) {
+                    OData.read(params.service + "$metadata", measurePost, unexpectedErrorHandler, OData.metadataHandler);
+                } else {
+                    measurePost();
+                }
+            }, "Time to POST " + params.uri + " with " + params.mimeType, params);
+
+            djstest.addTest(function postParallelMemoryTest(params) {
+                var total = 10;
+                var count = 0;
+                var measurePost = function (metadata) {
+                    Instrument.getBrowserMemorySize(function (memoryBefore) {
+                        for (var i = 0; i < total; i++) {
+                            OData.request(params.request, function (_, response) {
+                                count++;
+                                if (count >= total) {
+                                    Instrument.getBrowserMemorySize(function (memoryAfter) {
+                                        var memory = memoryAfter - memoryBefore;
+                                        djstest.pass("Memory: " + memory + " bytes (Network: " + response.headers["Content-Length"] + " bytes)");
+                                        djstest.done();
+                                    });
+                                }
+                            }, unexpectedErrorHandler, undefined, undefined, metadata);
+                        }
+                    });
+                };
+
+                OData.defaultHttpClient = timedHttpClient;
+                djstest.assertsExpected(1);
+
+                if (params.metadata) {
+                    OData.read(params.service + "$metadata", measurePost, unexpectedErrorHandler, OData.metadataHandler);
+                } else {
+                    measurePost();
+                }
+            }, "Memory to POST (x10 parallel) " + params.uri + " with " + params.mimeType, params);
+
+            djstest.addTest(function postSerialMemoryTest(params) {
+                var total = 10;
+                var count = 0;
+                var measurePost = function (metadata) {
+                    Instrument.getBrowserMemorySize(function (memoryBefore) {
+                        var makeRequest = function () {
+                            OData.request(params.request, function (_, response) {
+                                count++;
+                                if (count < total) {
+                                    setTimeout(makeRequest, 0);
+                                } else {
+                                    Instrument.getBrowserMemorySize(function (memoryAfter) {
+                                        var memory = memoryAfter - memoryBefore;
+                                        djstest.pass("Memory: " + memory + " bytes (Network: " + response.headers["Content-Length"] + " bytes)");
+                                        djstest.done();
+                                    });
+                                }
+                            }, unexpectedErrorHandler, undefined, undefined, metadata);
+                        };
+
+                        makeRequest();
+                    });
+                };
+
+                OData.defaultHttpClient = timedHttpClient;
+                djstest.assertsExpected(1);
+
+                if (params.metadata) {
+                    OData.read(params.service + "$metadata", measurePost, unexpectedErrorHandler, OData.metadataHandler);
+                } else {
+                    measurePost();
+                }
+            }, "Memory to POST (x10 serial) " + params.uri + " with " + params.mimeType, params);
+        }
+    });
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-read-crossdomain-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-read-crossdomain-functional-tests.html b/JSLib/tests/odata-read-crossdomain-functional-tests.html
new file mode 100644
index 0000000..f3e18a6
--- /dev/null
+++ b/JSLib/tests/odata-read-crossdomain-functional-tests.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>OData tests against local service</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/ODataReadOracle.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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+    
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="odata-read-crossdomain-functional-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">OData.Read tests against cross domain endpoints</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-read-crossdomain-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-read-crossdomain-functional-tests.js b/JSLib/tests/odata-read-crossdomain-functional-tests.js
new file mode 100644
index 0000000..7d1a013
--- /dev/null
+++ b/JSLib/tests/odata-read-crossdomain-functional-tests.js
@@ -0,0 +1,137 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="../src/odata-net.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    var fixConstructors = function (obj) {
+        /// <summary>Fix the constructors of the supplied object graph.</summary>
+        /// <remarks>
+        /// When using IE9 or a non-IE browser, the JSONP support in the library creates objects in a separate IFRAME,
+        /// causing the constructor property to be different to that of objects created by the oracle. This function
+        /// stringifies and then re-parses the object, which fixes the constructors.
+        /// </remarks>
+        if (!window.ActiveXObject || window.DOMParser) {
+            return window.JSON.parse(window.JSON.stringify(obj));
+        } else {
+            return obj;
+        }
+    };
+
+    var handlerAcceptStrings = [
+        "*/*",
+    /*"application/atom+xml",*/
+        "application/json",
+        undefined
+    ];
+
+    var formatJsonStrings = [
+        "application/json",
+        "application/json;odata.metadata=none",
+        "application/json;odata.metadata=minimal",
+        "application/json;odata.metadata=full",
+        undefined
+    ];
+    var azureOdataService = "http://odatasampleservices.azurewebsites.net/V4/OData/OData.svc/";
+    var azureOdataFeed = azureOdataService + "Categories";
+    var crossDomainTimeout = 45000;
+
+    module("CrossDomain", {
+        setup: function () {
+            this.oldEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+            OData.defaultHttpClient.enableJsonpCallback = true;
+        },
+        teardown: function () {
+            OData.defaultHttpClient.enableJsonpCallback = this.oldEnableJsonpCallback;
+        }
+    });
+
+    for (var i = 0; i < handlerAcceptStrings.length; i++) {
+        for (var j = 0; j < formatJsonStrings.length; j++) {
+            djstest.addTest(function readCrossDomainFullFeedTest(params) {
+                djstest.assertsExpected(1);
+                var request = { requestUri: azureOdataFeed, headers: { Accept: params.handlerAccept}, enableJsonpCallback: true };
+                if (params.formatJsonString != undefined) {
+                    request.formatQueryString = "$format=" + params.formatJsonString;
+                }
+
+                djstest.log("Reading data over the wire.");
+                OData.read(request, function (data, response) {
+                    djstest.log("Verifying data over the wire from Oracle.");
+                    window.ODataReadOracle.readFeed(azureOdataFeed, function (expectedData) {
+                        data = fixConstructors(data);
+                        djstest.assertWithoutMetadata(data, expectedData, "Response data not same as expected");
+                        djstest.done();
+                    }, params.formatJsonString);
+                }, unexpectedErrorHandler);
+            }, "Testing valid read of cross domain feed collection with " + handlerAcceptStrings[i] + "," + formatJsonStrings[j], { handlerAccept: handlerAcceptStrings[i], formatJsonString: formatJsonStrings[j] }, crossDomainTimeout);
+        }
+
+        djstest.addTest(function readCrossDomainEntryTest(handlerAccept) {
+            var endPoint = azureOdataFeed + "(1)";
+            djstest.assertsExpected(1);
+            djstest.log("Reading data over the wire.");
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} }, function (data, response) {
+                djstest.log("Verifying data over the wire from Oracle.");
+                window.ODataReadOracle.readEntry(endPoint, function (expectedData) {
+                    data = fixConstructors(data);
+                    djstest.assertWithoutMetadata(data, expectedData, "Response data not same as expected");
+                    djstest.done();
+                }, "application/json");
+            }, unexpectedErrorHandler);
+        }, "Testing valid read of cross domain entry with " + handlerAcceptStrings[i], handlerAcceptStrings[i], crossDomainTimeout);
+    }
+
+    var prefetchSizes = [-1, 0, 15];
+    var cacheSizes = [-1, 0, 15];
+    var skipValues = [
+        0,
+        14, // < pageSize
+        15, // = pageSize
+        16 // > pageSize but < pageSize + prefetchSize
+    ];
+
+    var createTestName = function (params) {
+        return "Testing ReadRange of " + params.feed + " skip " + params.skip + " take " + params.take + " with pageSize " +
+            params.pageSize + ", prefetch " + params.prefetchSize + " and cacheSize " + params.cacheSize;
+    };
+
+    var dataCacheReadRangeSingleTest = function (params) {
+        var options = { name: "cache", source: params.feed, pageSize: params.pageSize, prefetchSize: params.prefetchSize, cacheSize: params.cacheSize };
+        OData.defaultHttpClient.enableJsonpCallback = true;
+        var cache = datajs.createDataCache(options);
+        cache.readRange(params.skip, params.take).then(function (data) {
+            validateExpectedRange(cache, data, params.feed, params.skip, params.take);
+        }, unexpectedErrorHandler);
+    };
+
+    var validateExpectedRange = function (cache, data, feed, skipValue, takeValue) {
+        var expectedRangeUrl = feed + "?$skip=" + skipValue + "&$top=" + takeValue;
+        window.ODataReadOracle.readFeed(expectedRangeUrl, function (expectedData) {
+            if (expectedData.results) {
+                expectedData = expectedData.results;
+            }
+            data = fixConstructors(data);
+            djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+            djstest.destroyCacheAndDone(cache);
+        });
+    };
+
+    $.each(prefetchSizes, function (_, prefetchSizeValue) {
+        $.each(cacheSizes, function (_, cacheSizeValue) {
+            var parameters = { feed: "http://odatasampleservices.azurewebsites.net/V4/OData/OData.svc/Categories", skip: 0, take: 5, pageSize: 15, prefetchSize: prefetchSizeValue, cacheSize: cacheSizeValue };
+            djstest.addTest(dataCacheReadRangeSingleTest, createTestName(parameters), parameters);
+        });
+    });
+
+    $.each(skipValues, function (_, skipValue) {
+        var parameters = { feed: "http://odatasampleservices.azurewebsites.net/V4/OData/OData.svc/Categories", skip: skipValue, take: 14, pageSize: 5, prefetchSize: 5, cacheSize: 5 };
+        djstest.addTest(dataCacheReadRangeSingleTest, createTestName(parameters), parameters, crossDomainTimeout);
+    });
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-read-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-read-functional-tests.html b/JSLib/tests/odata-read-functional-tests.html
new file mode 100644
index 0000000..0a60a3a
--- /dev/null
+++ b/JSLib/tests/odata-read-functional-tests.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>OData tests against local service</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/ODataReadOracle.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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="common/djstest.js"></script>
+
+    <script type="text/javascript" src="odata-read-functional-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">OData.Read tests against local in-memory service</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>


[06/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-handler-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-handler-tests.js b/JSLib/tests/odata-handler-tests.js
new file mode 100644
index 0000000..7ec8a07
--- /dev/null
+++ b/JSLib/tests/odata-handler-tests.js
@@ -0,0 +1,319 @@
+/// <reference path="../src/odata-handler.js"/>
+/// <reference path="../src/odata.js"/>
+/// <reference path="imports/jquery.js"/>
+/// <reference path="common/djstest.js" />
+
+// odata-handler-tests.js
+
+(function (window, undefined) {
+
+    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";
+            
+            OData.request({
+                method: "PUT",
+                data: testItem,
+                requestUri: uri,
+            }, itemUpdatedCallback);
+        };
+
+        var itemUpdatedCallback = function (data, response) {
+            djstest.assertAreEqual(response.statusCode, 204, "Expecting no content on update");
+            OData.request({
+                method: "DELETE",
+                requestUri: uri
+            }, itemDeletedCallback);
+        };
+
+        var itemDeletedCallback = function (data, response) {
+            djstest.done();
+        };
+
+        $.post(serviceUri + "ResetData", function () {
+            OData.request({
+                requestUri: baseUri,
+                method: "POST",
+                data: { CategoryID: 1001, Name: "Name #1001" }
+            }, itemCreatedCallback);
+        });
+    });
+
+    djstest.addTest(function errorHandlerTest() {
+        djstest.assertsExpected(1);
+        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" }
+        });
+        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" }
+        });
+        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"} });
+        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"} });
+        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: OData.contentType("application/json"), dataServiceVersion: "4.0" }
+            },
+            {
+                response: { headers: { "Content-Type": "application/json" }, body: "response 1" },
+                shouldHit: true,
+                context: { contentType: OData.contentType("application/json"), dataServiceVersion: "" }
+            },
+            {
+                response: { headers: { "Content-Type": "otherMediaType" }, body: "response 2" },
+                shouldHit: false,
+                context: { contentType: OData.contentType("otherMediaType"), dataServiceVersion: "" }
+            },
+            {
+                response: { headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, body: "response 3" },
+                shouldHit: true,
+                context: { contentType: OData.contentType("application/json"), dataServiceVersion: "4.0" }
+            },
+            {
+                response: { body: "response 4" },
+                shouldHit: false,
+                context: { contentType: OData.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 = OData.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: OData.contentType("application/json"), dataServiceVersion: "4.0" }
+            },
+            {
+                request: { headers: { "Content-Type": "application/json" }, data: "request 1" },
+                shouldHit: true,
+                context: { contentType: OData.contentType("application/json"), dataServiceVersion: undefined }
+            },
+            {
+                request: { headers: { "Content-Type": "otherMediaType" }, data: "request 2" },
+                shouldHit: false,
+                context: { contentType: OData.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: OData.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 = OData.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 = OData.contentType("my new content type");
+            return data;
+        };
+
+        var testHandler = OData.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/atom+xml;param1=value1;param2=value2", expected: { mediaType: "application/atom+xml", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/atom+xml; param1=value1; param2=value2", expected: { mediaType: "application/atom+xml", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/atom+xml;param1=value1; param2=value2", expected: { mediaType: "application/atom+xml", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/atom+xml; param1=value1;param2=value2", expected: { mediaType: "application/atom+xml", properties: { param1: "value1", param2: "value2"}} },
+            { contentType: "application/atom+xml", expected: { mediaType: "application/atom+xml", 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 = OData.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(!OData.contentType(undefined), "contentType returns undefined for undefined input");
+        djstest.assert(!OData.contentType(null), "contentType returns undefined for null input");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function contentTypeToStringTest() {
+        var tests = [
+            { contentType: { mediaType: "application/atom+xml", properties: { param1: "value1", param2: "value2"} }, expected: "application/atom+xml;param1=value1;param2=value2" },
+            { contentType: { mediaType: "application/atom+xml", properties: {} }, expected: "application/atom+xml" },
+            { 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 = OData.contentTypeToString(tests[i].contentType);
+            djstest.assertAreEqual(cTypeString, tests[i].expected, "contentTypeToString returns the correct contentType string");
+        }
+
+        djstest.done();
+    });
+    // DATAJS INTERNAL END
+})(this);
\ No newline at end of file


[09/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/mockXMLHttpRequest.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/mockXMLHttpRequest.js b/JSLib/tests/common/mockXMLHttpRequest.js
new file mode 100644
index 0000000..2615e55
--- /dev/null
+++ b/JSLib/tests/common/mockXMLHttpRequest.js
@@ -0,0 +1,192 @@
+// mockXMLHttpRequest.js
+//
+// This file provides a window.MockXMLHttpRequest object that can be used
+// to replace or wrap the browser's XMLHttpRequest object for testing.
+//
+// Typically the object is installed, a test run, and then the original
+// object restored. The addResponse and addRequest verifier can be
+// used to set up callbacks; reset is used to clear those values.
+//
+// For a sample demonstrating how to use this functionality, see
+// the httpClientSendRequestTest test in odata-net-tests.js.
+
+(function (window, undefined) {
+
+    if (!window.MockXMLHttpRequest) {
+        window.MockXMLHttpRequest = {};
+    }
+
+    var mockXMLHttpRequest = window.MockXMLHttpRequest;
+
+    var responses = {};
+    var verifiers = {};
+
+    mockXMLHttpRequest.addResponse = function (uri, response) {
+        /// <summary>Adds a new response to be returned for the specified uri (* for 'anything').</summary>
+        /// <param name="uri" type="String">URI to match (* to match anything not otherwise specified).</param>
+        /// <param name="response" type="Object">Response object.</param>
+        responses = responses || {};
+        responses[uri] = response;
+
+        return this;
+    };
+
+    mockXMLHttpRequest.addRequestVerifier = function (uri, verifier) {
+        /// <summary>Adds a new request verifier to be invoked for the specified uri (* for 'anything').</summary>
+        /// <param name="uri" type="String">URI to match (* to match anything not otherwise specified).</param>
+        /// <param name="response" type="Function">Verifier callback that takes the request.</param>
+
+        verifiers = verifiers || {};
+        verifiers[uri] = verifier;
+
+        return this;
+    };
+
+    mockXMLHttpRequest.reset = function () {
+        /// <summary>Resets all configuration from the mock XHR object.</summary>
+
+        responses = {};
+        verifiers = {};
+
+        mockXMLHttpRequest.onCreate = undefined;
+        mockXMLHttpRequest.onAfterSend = undefined;
+
+        return this;
+    };
+
+    var xmlHttpRequest = function () {
+        //properties
+        this.readyState = 0;
+        this.responseXML = undefined;
+
+        //events
+        this.ontimeout = undefined;
+        this.onreadystatechange = undefined;
+
+        if (mockXMLHttpRequest.onCreate) {
+            mockXMLHttpRequest.onCreate(this);
+        }
+    };
+
+    xmlHttpRequest.prototype.open = function (method, url, async, user, password) {
+        if (!method) {
+            throw { method: "method parameter is not defined, empty, or null " };
+        }
+        if (!url) {
+            throw { message: "url parameter is not defined, empty, or null " };
+        }
+
+        this._request = {
+            headers: {},
+            url: url,
+            method: method,
+            async: async,
+            user: user,
+            password: password
+        };
+    };
+
+    xmlHttpRequest.prototype.getAllResponseHeaders = function () {
+        if (!this._response) {
+            throw { message: "_response property is undefined, did you forget to call send() or map the request url to a response?" };
+        }
+
+        var result = "";
+        var header;
+        for (header in this._response.headers) {
+            result = result + header + ": " + this._response.headers[header] + "\n\r";
+        }
+        //remove trailing LFCR
+        return result.substring(0, result.length - 2);
+    };
+
+    xmlHttpRequest.prototype.getResponseHeader = function (header) {
+        if (!this._response) {
+            throw { message: "_response property is undefined, did you forget to call send() or map the request url to a response?" };
+        }
+        return this._response.headers[header];
+    };
+
+    xmlHttpRequest.prototype.abort = function () {
+        //do nothing for now.
+    };
+
+    xmlHttpRequest.prototype.setRequestHeader = function (header, value) {
+        if (!this._request) {
+            throw { message: "_request property is undefined, did you forget to call open() first?" };
+        }
+        this._request.headers[header] = value;
+    };
+
+    xmlHttpRequest.prototype.send = function (data) {
+        if (!this._request) {
+            throw { message: "_request property is undefined, did you forget to call open() first?" };
+        }
+
+        if (this._request.headers["MockNoOp"]) {
+            return;
+        }
+
+        if (this._request.headers["MockTimeOut"]) {
+            if (!this.timeout) {
+                throw { message: "timeout property is not set" };
+            }
+
+            if (this.ontimeout) {
+                (function (xhr) {
+                    setTimeout(function () {
+                        xhr.ontimeout();
+                    }, xhr.timeout);
+                })(this);
+            }
+            return;
+        }
+
+        var url = this._request.url;
+        var verifier = verifiers[url];
+        var response = responses[url];
+
+        if (!verifier) {
+            verifier = verifiers["*"];
+        }
+
+        if (!response) {
+            response = responses["*"];
+        }
+
+        if (!verifier && !response) {
+            throw { message: "neither verifier or response defined for url: " + url };
+        }
+
+        this._request.body = data;
+
+        if (verifier) {
+            verifier(this._request);
+        }
+
+        if (response) {
+            // Execute the respone after a 30ms delay.
+            this._response = response;
+            sendResponseDelay(this, response, 60);
+        }
+    };
+
+    var sendResponseDelay = function (xhr, response, delay) {
+        setTimeout(function () {
+            xhr.status = response.status;
+            xhr.responseText = response.body;
+            xhr.responseBody = response.body;
+
+            xhr.readyState = 4;
+            if (xhr.onreadystatechange) {
+                xhr.onreadystatechange();
+                if (mockXMLHttpRequest.onAfterSend) {
+                    mockXMLHttpRequest.onAfterSend();
+                }
+            }
+        }, delay);
+    };
+
+    mockXMLHttpRequest.XMLHttpRequest = xmlHttpRequest;
+
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/rx.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/rx.js b/JSLib/tests/common/rx.js
new file mode 100644
index 0000000..a7f4ea3
--- /dev/null
+++ b/JSLib/tests/common/rx.js
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// This code is licensed by Microsoft Corporation under the terms
+// of the MICROSOFT REACTIVE EXTENSIONS FOR JAVASCRIPT AND .NET LIBRARIES License.
+// See http://go.microsoft.com/fwlink/?LinkId=186234.
+
+(function(){var a;var b;var c=this;var d="Index out of range";if(typeof ProvideCustomRxRootObject =="undefined")b=c.Rx={}; else b=ProvideCustomRxRootObject();var e=function(){};var f=function(){return new Date().getTime();};var g=function(r0,s0){return r0===s0;};var h=function(r0){return r0;};var i=function(r0){return {Dispose:r0};};var j={Dispose:e};b.Disposable={Create:i,Empty:j};var k=b.BooleanDisposable=function(){var r0=false;this.GetIsDisposed=function(){return r0;};this.Dispose=function(){r0=true;};};var l=function(r0){var s0=false;r0.a++;this.Dispose=function(){var t0=false;if(!r0.b){if(!this.c){this.c=true;r0.a--;if(r0.a==0&&r0.d){r0.b=true;t0=true;}}}if(t0)r0.e.Dispose();};};var m=b.RefCountDisposable=function(r0){this.d=false;this.b=false;this.e=r0;this.a=0;this.Dispose=function(){var s0=false;if(!this.b){if(!this.d){this.d=true;if(this.a==0){this.b=true;s0=true;}}}if(s0)this.e.Dispose();};this.GetDisposable=function(){if(this.b)return j; else return new l(this);};};var n
 =b.CompositeDisposable=function(){var r0=new q();for(var s0=0;s0<arguments.length;s0++) r0.Add(arguments[s0]);var t0=false;this.GetCount=function(){return r0.GetCount();};this.Add=function(u0){if(!t0)r0.Add(u0); else u0.Dispose();};this.Remove=function(u0,v0){if(!t0){var w0=r0.Remove(u0);if(!v0&w0)u0.Dispose();}};this.Dispose=function(){if(!t0){t0=true;this.Clear();}};this.Clear=function(){for(var u0=0;u0<r0.GetCount();u0++) r0.GetItem(u0).Dispose();r0.Clear();};};var o=b.MutableDisposable=function(){var r0=false;var s0;this.Get=function(){return s0;},this.Replace=function(t0){if(r0&&t0!==a)t0.Dispose(); else{if(s0!==a)s0.Dispose();s0=t0;}};this.Dispose=function(){if(!r0){r0=true;if(s0!==a)s0.Dispose();}};};var p=function(r0){var s0=[];for(var t0=0;t0<r0.length;t0++) s0.push(r0[t0]);return s0;};var q=b.List=function(r0){var s0=[];var t0=0;var u0=r0!==a?r0:g;this.Add=function(v0){s0[t0]=v0;t0++;};this.RemoveAt=function(v0){if(v0<0||v0>=t0)throw d;if(v0==0){s0.shift();t0--;}else{s0.sp
 lice(v0,1);t0--;}};this.IndexOf=function(v0){for(var w0=0;w0<t0;w0++){if(u0(v0,s0[w0]))return w0;}return -1;};this.Remove=function(v0){var w0=this.IndexOf(v0);if(w0==-1)return false;this.RemoveAt(w0);return true;};this.Clear=function(){s0=[];t0=0;};this.GetCount=function(){return t0;};this.GetItem=function(v0){if(v0<0||v0>=t0)throw d;return s0[v0];};this.SetItem=function(v0,w0){if(v0<0||v0>=t0)throw d;s0[v0]=w0;};this.ToArray=function(){var v0=[];for(var w0=0;w0<this.GetCount();w0++) v0.push(this.GetItem(w0));return v0;};};var r=function(r0){if(r0===null)r0=g;this.f=r0;var s0=4;this.g=new Array(s0);this.h=0;};r.prototype.i=function(r0,s0){return this.f(this.g[r0],this.g[s0])<0;};r.prototype.j=function(r0){if(r0>=this.h||r0<0)return;var s0=r0-1>>1;if(s0<0||s0==r0)return;if(this.i(r0,s0)){var t0=this.g[r0];this.g[r0]=this.g[s0];this.g[s0]=t0;this.j(s0);}};r.prototype.k=function(r0){if(r0===a)r0=0;var s0=2*r0+1;var t0=2*r0+2;var u0=r0;if(s0<this.h&&this.i(s0,u0))u0=s0;if(t0<this.h&&thi
 s.i(t0,u0))u0=t0;if(u0!=r0){var v0=this.g[r0];this.g[r0]=this.g[u0];this.g[u0]=v0;this.k(u0);}};r.prototype.GetCount=function(){return this.h;};r.prototype.Peek=function(){if(this.h==0)throw "Heap is empty.";return this.g[0];};r.prototype.Dequeue=function(){var r0=this.Peek();this.g[0]=this.g[--this.h];delete this.g[this.h];this.k();return r0;};r.prototype.Enqueue=function(r0){var s0=this.h++;this.g[s0]=r0;this.j(s0);};var s=b.Scheduler=function(r0,s0,t0){this.Schedule=r0;this.ScheduleWithTime=s0;this.Now=t0;this.ScheduleRecursive=function(u0){var v0=this;var w0=new n();var x0;x0=function(){u0(function(){var y0=false;var z0=false;var A0;A0=v0.Schedule(function(){x0();if(y0)w0.Remove(A0); else z0=true;});if(!z0){w0.Add(A0);y0=true;}});};w0.Add(v0.Schedule(x0));return w0;};this.ScheduleRecursiveWithTime=function(u0,v0){var w0=this;var x0=new n();var y0;y0=function(){u0(function(z0){var A0=false;var B0=false;var C0;C0=w0.ScheduleWithTime(function(){y0();if(A0)x0.Remove(C0); else B0=tru
 e;},z0);if(!B0){x0.Add(C0);A0=true;}});};x0.Add(w0.ScheduleWithTime(y0,v0));return x0;};};var t=b.VirtualScheduler=function(r0,s0,t0,u0){var v0=new s(function(w0){return this.ScheduleWithTime(w0,0);},function(w0,x0){return this.ScheduleVirtual(w0,u0(x0));},function(){return t0(this.l);});v0.ScheduleVirtual=function(w0,x0){var y0=new k();var z0=s0(this.l,x0);var A0=function(){if(!y0.IsDisposed)w0();};var B0=new y(A0,z0);this.m.Enqueue(B0);return y0;};v0.Run=function(){while(this.m.GetCount()>0){var w0=this.m.Dequeue();this.l=w0.n;w0.o();}};v0.RunTo=function(w0){while(this.m.GetCount()>0&&this.f(this.m.Peek().n,w0)<=0){var x0=this.m.Dequeue();this.l=x0.n;x0.o();}};v0.GetTicks=function(){return this.l;};v0.l=0;v0.m=new r(function(w0,x0){return r0(w0.n,x0.n);});v0.f=r0;return v0;};var u=b.TestScheduler=function(){var r0=new t(function(s0,t0){return s0-t0;},function(s0,t0){return s0+t0;},function(s0){return new Date(s0);},function(s0){if(s0<=0)return 1;return s0;});return r0;};var v=new 
 s(function(r0){return this.ScheduleWithTime(r0,0);},function(r0,s0){var t0=this.Now()+s0;var u0=new y(r0,t0);if(this.m===a){var v0=new w();try{this.m.Enqueue(u0);v0.p();}finally{v0.q();}}else this.m.Enqueue(u0);return u0.r();},f);v.s=function(r0){if(this.m===a){var s0=new w();try{r0();s0.p();}finally{s0.q();}}else r0();};s.CurrentThread=v;var w=function(){v.m=new r(function(r0,s0){try{return r0.n-s0.n;}catch(t0){debugger;}});this.q=function(){v.m=a;};this.p=function(){while(v.m.GetCount()>0){var r0=v.m.Dequeue();if(!r0.t()){while(r0.n-v.Now()>0);if(!r0.t())r0.o();}}};};var x=0;var y=function(r0,s0){this.u=x++;this.o=r0;this.n=s0;this.v=new k();this.t=function(){return this.v.GetIsDisposed();};this.r=function(){return this.v;};};var z=new s(function(r0){r0();return j;},function(r0,s0){while(this.Now<s0);r0();},f);s.Immediate=z;var A=new s(function(r0){var s0=c.setTimeout(r0,0);return i(function(){c.clearTimeout(s0);});},function(r0,s0){var t0=c.setTimeout(r0,s0);return i(function(){c
 .clearTimeout(t0);});},f);s.Timeout=A;var B=b.Observer=function(r0,s0,t0){this.OnNext=r0===a?e:r0;this.OnError=s0===a?function(u0){throw u0;}:s0;this.OnCompleted=t0===a?e:t0;this.AsObserver=function(){var u0=this;return new B(function(v0){u0.OnNext(v0);},function(v0){u0.OnError(v0);},function(){u0.OnCompleted();});};};var C=B.Create=function(r0,s0,t0){return new B(r0,s0,t0);};var D=b.Observable=function(r0){this.w=r0;};var E=D.CreateWithDisposable=function(r0){return new D(r0);};var F=D.Create=function(r0){return E(function(s0){return i(r0(s0));});};var G=function(){return this.Select(function(r0){return r0.Value;});};D.prototype={Subscribe:function(r0,s0,t0){var u0;if(arguments.length==0||arguments.length>1||typeof r0 =="function")u0=new B(r0,s0,t0); else u0=r0;return this.x(u0);},x:function(r0){var s0=false;var t0=new o();var u0=this;v.s(function(){var v0=new B(function(w0){if(!s0)r0.OnNext(w0);},function(w0){if(!s0){s0=true;t0.Dispose();r0.OnError(w0);}},function(){if(!s0){s0=tru
 e;t0.Dispose();r0.OnCompleted();}});t0.Replace(u0.w(v0));});return new n(t0,i(function(){s0=true;}));},Select:function(r0){var s0=this;return E(function(t0){var u0=0;return s0.Subscribe(new B(function(v0){var w0;try{w0=r0(v0,u0++);}catch(x0){t0.OnError(x0);return;}t0.OnNext(w0);},function(v0){t0.OnError(v0);},function(){t0.OnCompleted();}));});},Let:function(r0,s0){if(s0===a)return r0(this);var t0=this;return E(function(u0){var v0=s0();var w0;try{w0=r0(v0);}catch(A0){return L(A0).Subscribe(u0);}var x0=new o();var y0=new o();var z0=new n(y0,x0);x0.Replace(w0.Subscribe(function(A0){u0.OnNext(A0);},function(A0){u0.OnError(A0);z0.Dispose();},function(){u0.OnCompleted();z0.Dispose();}));y0.Replace(t0.Subscribe(v0));return z0;});},MergeObservable:function(){var r0=this;return E(function(s0){var t0=false;var u0=new n();var v0=new o();u0.Add(v0);v0.Replace(r0.Subscribe(function(w0){var x0=new o();u0.Add(x0);x0.Replace(w0.Subscribe(function(y0){s0.OnNext(y0);},function(y0){s0.OnError(y0);},f
 unction(){u0.Remove(x0);if(u0.GetCount()==1&&t0)s0.OnCompleted();}));},function(w0){s0.OnError(w0);},function(){t0=true;if(u0.GetCount()==1)s0.OnCompleted();}));return u0;});},y:function(r0,s0){var t0=p(s0);t0.unshift(this);return r0(t0);},Concat:function(){return this.y(I,arguments);},Merge:function(){return this.y(H,arguments);},Catch:function(){return this.y(P,arguments);},OnErrorResumeNext:function(){return this.y(V,arguments);},Zip:function(r0,s0){var t0=this;return E(function(u0){var v0=false;var w0=[];var x0=[];var y0=false;var z0=false;var A0=new n();var B0=function(C0){A0.Dispose();w0=a;x0=a;u0.OnError(C0);};A0.Add(t0.Subscribe(function(C0){if(z0){u0.OnCompleted();return;}if(x0.length>0){var D0=x0.shift();var E0;try{E0=s0(C0,D0);}catch(F0){A0.Dispose();u0.OnError(F0);return;}u0.OnNext(E0);}else w0.push(C0);},B0,function(){if(z0){u0.OnCompleted();return;}y0=true;}));A0.Add(r0.Subscribe(function(C0){if(y0){u0.OnCompleted();return;}if(w0.length>0){var D0=w0.shift();var E0;try{
 E0=s0(D0,C0);}catch(F0){A0.Dispose();u0.OnError(F0);return;}u0.OnNext(E0);}else x0.push(C0);},B0,function(){if(y0){u0.OnCompleted();return;}z0=true;}));return A0;});},CombineLatest:function(r0,s0){var t0=this;return E(function(u0){var v0=false;var w0=false;var x0=false;var y0;var z0;var A0=false;var B0=false;var C0=new n();var D0=function(E0){C0.Dispose();u0.OnError(E0);};C0.Add(t0.Subscribe(function(E0){if(B0){u0.OnCompleted();return;}if(x0){var F0;try{F0=s0(E0,z0);}catch(G0){C0.Dispose();u0.OnError(G0);return;}u0.OnNext(F0);}y0=E0;w0=true;},D0,function(){if(B0){u0.OnCompleted();return;}A0=true;}));C0.Add(r0.Subscribe(function(E0){if(A0){u0.OnCompleted();return;}if(w0){var F0;try{F0=s0(y0,E0);}catch(G0){C0.Dispose();u0.OnError(G0);return;}u0.OnNext(F0);}z0=E0;x0=true;},D0,function(){if(A0){u0.OnCompleted();return;}B0=true;}));});},Switch:function(){var r0=this;return E(function(s0){var t0=false;var u0=new o();var v0=new o();v0.Replace(r0.Subscribe(function(w0){if(!t0){var x0=new o(
 );x0.Replace(w0.Subscribe(function(y0){s0.OnNext(y0);},function(y0){v0.Dispose();u0.Dispose();s0.OnError(y0);},function(){u0.Replace(a);if(t0)s0.OnCompleted();}));u0.Replace(x0);}},function(w0){u0.Dispose();s0.OnError(w0);},function(){t0=true;if(u0.Get()===a)s0.OnCompleted();}));return new n(v0,u0);});},TakeUntil:function(r0){var s0=this;return E(function(t0){var u0=new n();u0.Add(r0.Subscribe(function(){t0.OnCompleted();u0.Dispose();},function(v0){t0.OnError(v0);},function(){}));u0.Add(s0.Subscribe(t0));return u0;});},SkipUntil:function(r0){var s0=this;return E(function(t0){var u0=true;var v0=new n();v0.Add(r0.Subscribe(function(){u0=false;},function(w0){t0.OnError(w0);},e));v0.Add(s0.Subscribe(new B(function(w0){if(!u0)t0.OnNext(w0);},function(w0){t0.OnError(w0);},function(){if(!u0)t0.OnCompleted();})));return v0;});},Scan1:function(r0){var s0=this;return O(function(){var t0;var u0=false;return s0.Select(function(v0){if(u0)t0=r0(t0,v0); else{t0=v0;u0=true;}return t0;});});},Scan:f
 unction(r0,s0){var t0=this;return O(function(){var u0;var v0=false;return t0.Select(function(w0){if(v0)u0=s0(u0,w0); else{u0=s0(r0,w0);v0=true;}return u0;});});},Scan0:function(r0,s0){var t0=this;return E(function(u0){var v0=r0;var w0=true;return t0.Subscribe(function(x0){if(w0){w0=false;u0.OnNext(v0);}try{v0=s0(v0,x0);}catch(y0){u0.OnError(y0);return;}u0.OnNext(v0);},function(x0){if(w0)u0.OnNext(v0);u0.OnError(x0);},function(){if(w0)u0.OnNext(v0);u0.OnCompleted();});});},Finally:function(r0){var s0=this;return F(function(t0){var u0=s0.Subscribe(t0);return function(){try{u0.Dispose();r0();}catch(v0){r0();throw v0;}};});},Do:function(r0,s0,t0){var u0;if(arguments.length==0||arguments.length>1||typeof r0 =="function")u0=new B(r0,s0!==a?s0:e,t0); else u0=r0;var v0=this;return E(function(w0){return v0.Subscribe(new B(function(x0){try{u0.OnNext(x0);}catch(y0){w0.OnError(y0);return;}w0.OnNext(x0);},function(x0){if(s0!==a)try{u0.OnError(x0);}catch(y0){w0.OnError(y0);return;}w0.OnError(x0);
 },function(){if(t0!==a)try{u0.OnCompleted();}catch(x0){w0.OnError(x0);return;}w0.OnCompleted();}));});},Where:function(r0){var s0=this;return E(function(t0){var u0=0;return s0.Subscribe(new B(function(v0){var w0=false;try{w0=r0(v0,u0++);}catch(x0){t0.OnError(x0);return;}if(w0)t0.OnNext(v0);},function(v0){t0.OnError(v0);},function(){t0.OnCompleted();}));});},Take:function(r0,s0){if(s0===a)s0=z;var t0=this;return E(function(u0){if(r0<=0){t0.Subscribe().Dispose();return N(s0).Subscribe(u0);}var v0=r0;return t0.Subscribe(new B(function(w0){if(v0-->0){u0.OnNext(w0);if(v0==0)u0.OnCompleted();}},function(w0){u0.OnError(w0);},function(){u0.OnCompleted();}));});},GroupBy:function(r0,s0,t0){if(r0===a)r0=h;if(s0===a)s0=h;if(t0===a)t0=function(v0){return v0.toString();};var u0=this;return E(function(v0){var w0={};var x0=new o();var y0=new m(x0);x0.Replace(u0.Subscribe(function(z0){var A0;try{A0=r0(z0);}catch(G0){for(var H0 in w0) w0[H0].OnError(G0);v0.OnError(G0);return;}var B0=false;var C0;try
 {var D0=t0(A0);if(w0[D0]===a){C0=new i0();w0[D0]=C0;B0=true;}else C0=w0[D0];}catch(G0){for(var H0 in w0) w0[H0].OnError(G0);v0.OnError(G0);return;}if(B0){var E0=E(function(G0){return new n(y0.GetDisposable(),C0.Subscribe(G0));});E0.Key=A0;v0.OnNext(E0);}var F0;try{F0=s0(z0);}catch(G0){for(var H0 in w0) w0[H0].OnError(G0);v0.OnError(G0);return;}C0.OnNext(F0);},function(z0){for(var A0 in w0) w0[A0].OnError(z0);v0.OnError(z0);},function(){for(var z0 in w0) w0[z0].OnCompleted();v0.OnCompleted();}));return y0;});},TakeWhile:function(r0){var s0=this;return E(function(t0){var u0=true;return s0.Subscribe(new B(function(v0){if(u0){try{u0=r0(v0);}catch(w0){t0.OnError(w0);return;}if(u0)t0.OnNext(v0); else t0.OnCompleted();}},function(v0){t0.OnError(v0);},function(){t0.OnCompleted();}));});},SkipWhile:function(r0){var s0=this;return E(function(t0){var u0=false;return s0.Subscribe(new B(function(v0){if(!u0)try{u0=!r0(v0);}catch(w0){t0.OnError(w0);return;}if(u0)t0.OnNext(v0);},function(v0){t0.OnE
 rror(v0);},function(){t0.OnCompleted();}));});},Skip:function(r0){var s0=this;return E(function(t0){var u0=r0;return s0.Subscribe(new B(function(v0){if(u0--<=0)t0.OnNext(v0);},function(v0){t0.OnError(v0);},function(){t0.OnCompleted();}));});},SelectMany:function(r0){return this.Select(r0).MergeObservable();},TimeInterval:function(r0){if(r0===a)r0=z;var s0=this;return O(function(){var t0=r0.Now();return s0.Select(function(u0){var v0=r0.Now();var w0=v0-t0;t0=v0;return {Interval:w0,Value:u0};});});},RemoveInterval:G,Timestamp:function(r0){if(r0===a)r0=z;return this.Select(function(s0){return {Timestamp:r0.Now(),Value:s0};});},RemoveTimestamp:G,Materialize:function(){var r0=this;return E(function(s0){return r0.Subscribe(new B(function(t0){s0.OnNext(new h0("N",t0));},function(t0){s0.OnNext(new h0("E",t0));s0.OnCompleted();},function(){s0.OnNext(new h0("C"));s0.OnCompleted();}));});},Dematerialize:function(){return this.SelectMany(function(r0){return r0;});},AsObservable:function(){var r0
 =this;return E(function(s0){return r0.Subscribe(s0);});},Delay:function(r0,s0){if(s0===a)s0=A;var t0=this;return E(function(u0){var v0=[];var w0=false;var x0=new o();var y0=t0.Materialize().Timestamp().Subscribe(function(z0){if(z0.Value.Kind=="E"){u0.OnError(z0.Value.Value);v0=[];if(w0)x0.Dispose();return;}v0.push({Timestamp:s0.Now()+r0,Value:z0.Value});if(!w0){x0.Replace(s0.ScheduleRecursiveWithTime(function(A0){var B0;do{B0=a;if(v0.length>0&&v0[0].Timestamp<=s0.Now())B0=v0.shift().Value;if(B0!==a)B0.Accept(u0);}while(B0!==a);if(v0.length>0){A0(Math.max(0,v0[0].Timestamp-s0.Now()));w0=true;}else w0=false;},r0));w0=true;}});return new n(y0,x0);});},Throttle:function(r0,s0){if(s0===a)s0=A;var t0=this;return E(function(u0){var v0;var w0=false;var x0=new o();var y0=0;var z0=t0.Subscribe(function(A0){w0=true;v0=A0;y0++;var B0=y0;x0.Replace(s0.ScheduleWithTime(function(){if(w0&&y0==B0)u0.OnNext(v0);w0=false;},r0));},function(A0){x0.Dispose();u0.OnError(A0);w0=false;y0++;},function(){x0.D
 ispose();if(w0)u0.OnNext(v0);u0.OnCompleted();w0=false;y0++;});return new n(z0,x0);});},Timeout:function(r0,s0,t0){if(t0===a)t0=A;if(s0===a)s0=L("Timeout",t0);var u0=this;return E(function(v0){var w0=new o();var x0=new o();var y0=0;var z0=y0;var A0=false;x0.Replace(t0.ScheduleWithTime(function(){A0=y0==z0;if(A0)w0.Replace(s0.Subscribe(v0));},r0));w0.Replace(u0.Subscribe(function(B0){var C0=0;if(!A0){y0++;C0=y0;v0.OnNext(B0);x0.Replace(t0.ScheduleWithTime(function(){A0=y0==C0;if(A0)w0.Replace(s0.Subscribe(v0));},r0));}},function(B0){if(!A0){y0++;v0.OnError(B0);}},function(){if(!A0){y0++;v0.OnCompleted();}}));return new n(w0,x0);});},Sample:function(r0,s0){if(s0===a)s0=A;var t0=this;return E(function(u0){var v0=false;var w0;var x0=false;var y0=new n();y0.Add(Y(r0,s0).Subscribe(function(z0){if(v0){u0.OnNext(w0);v0=false;}if(x0)u0.OnCompleted();},function(z0){u0.OnError(z0);},function(){u0.OnCompleted();}));y0.Add(t0.Subscribe(function(z0){v0=true;w0=z0;},function(z0){u0.OnError(z0);y0.
 Dispose();},function(){x0=true;}));return y0;});},Repeat:function(r0,s0){var t0=this;if(s0===a)s0=z;if(r0===a)r0=-1;return E(function(u0){var v0=r0;var w0=new o();var x0=new n(w0);var y0=function(z0){w0.Replace(t0.Subscribe(function(A0){u0.OnNext(A0);},function(A0){u0.OnError(A0);},function(){if(v0>0){v0--;if(v0==0){u0.OnCompleted();return;}}z0();}));};x0.Add(s0.ScheduleRecursive(y0));return x0;});},Retry:function(r0,s0){var t0=this;if(s0===a)s0=z;if(r0===a)r0=-1;return E(function(u0){var v0=r0;var w0=new o();var x0=new n(w0);var y0=function(z0){w0.Replace(t0.Subscribe(function(A0){u0.OnNext(A0);},function(A0){if(v0>0){v0--;if(v0==0){u0.OnError(A0);return;}}z0();},function(){u0.OnCompleted();}));};x0.Add(s0.ScheduleRecursive(y0));return x0;});},BufferWithTime:function(r0,s0,t0){if(t0===a)t0=A;if(s0===a)s0=r0;var u0=this;return E(function(v0){var w0=new q();var x0=t0.Now();var y0=function(){var C0=[];for(var D0=0;D0<w0.GetCount();D0++){var E0=w0.GetItem(D0);if(E0.Timestamp-x0>=0)C0.p
 ush(E0.Value);}return C0;};var z0=new n();var A0=function(C0){v0.OnError(C0);};var B0=function(){v0.OnNext(y0());v0.OnCompleted();};z0.Add(u0.Subscribe(function(C0){w0.Add({Value:C0,Timestamp:t0.Now()});},A0,B0));z0.Add(a0(r0,s0,t0).Subscribe(function(C0){var D0=y0();var E0=t0.Now()+s0-r0;while(w0.GetCount()>0&&w0.GetItem(0).Timestamp-E0<=0)w0.RemoveAt(0);v0.OnNext(D0);x0=E0;},A0,B0));return z0;});},BufferWithTimeOrCount:function(r0,s0,t0){if(t0===a)t0=A;var u0=this;return E(function(v0){var w0=0;var x0=new q();var y0=function(){v0.OnNext(x0.ToArray());x0.Clear();w0++;};var z0=new o();var A0;A0=function(C0){var D0=t0.ScheduleWithTime(function(){var E0=false;var F0=0;if(C0==w0){y0();F0=w0;E0=true;}if(E0)A0(F0);},r0);z0.Replace(D0);};A0(w0);var B0=u0.Subscribe(function(C0){var D0=false;var E0=0;x0.Add(C0);if(x0.GetCount()==s0){y0();E0=w0;D0=true;}if(D0)A0(E0);},function(C0){v0.OnError(C0);x0.Clear();},function(){v0.OnNext(x0.ToArray());w0++;v0.OnCompleted();x0.Clear();});return new n(
 B0,z0);});},BufferWithCount:function(r0,s0){if(s0===a)s0=r0;var t0=this;return E(function(u0){var v0=[];var w0=0;return t0.Subscribe(function(x0){if(w0==0)v0.push(x0); else w0--;var y0=v0.length;if(y0==r0){var z0=v0;v0=[];var A0=Math.min(s0,y0);for(var B0=A0;B0<y0;B0++) v0.push(z0[B0]);w0=Math.max(0,s0-r0);u0.OnNext(z0);}},function(x0){u0.OnError(x0);},function(){if(v0.length>0)u0.OnNext(v0);u0.OnCompleted();});});},StartWith:function(r0,s0){if(!(r0 instanceof Array))r0=[r0];if(s0===a)s0=z;var t0=this;return E(function(u0){var v0=new n();var w0=0;v0.Add(s0.ScheduleRecursive(function(x0){if(w0<r0.length){u0.OnNext(r0[w0]);w0++;x0();}else v0.Add(t0.Subscribe(u0));}));return v0;});},DistinctUntilChanged:function(r0,s0){if(r0===a)r0=h;if(s0===a)s0=g;var t0=this;return E(function(u0){var v0;var w0=false;return t0.Subscribe(function(x0){var y0;try{y0=r0(x0);}catch(A0){u0.OnError(A0);return;}var z0=false;if(w0)try{z0=s0(v0,y0);}catch(A0){u0.OnError(A0);return;}if(!w0||!z0){w0=true;v0=y0;u0
 .OnNext(x0);}},function(x0){u0.OnError(x0);},function(){u0.OnCompleted();});});},Publish:function(r0){if(r0===a)return new q0(this,new i0());var s0=this;return E(function(t0){var u0=new q0(s0,new i0());return new n(r0(u0).Subscribe(B),u0.Connect());});},Prune:function(r0,s0){if(s0===a)s0=z;if(r0===a)return new q0(this,new k0(s0));var t0=this;return E(function(u0){var v0=new q0(t0,new k0(s0));return new n(r0(v0).Subscribe(B),v0.Connect());});},Replay:function(r0,s0,t0,u0){if(u0===a)u0=v;if(r0===a)return new q0(this,new m0(s0,t0,u0));var v0=this;return E(function(w0){var x0=new q0(v0,new m0(s0,t0,u0));return new n(r0(x0).Subscribe(B),x0.Connect());});},SkipLast:function(r0){var s0=this;return E(function(t0){var u0=[];return s0.Subscribe(function(v0){u0.push(v0);if(u0.length>r0)t0.OnNext(u0.shift());},function(v0){t0.OnError(v0);},function(){t0.OnCompleted();});});},TakeLast:function(r0){var s0=this;return E(function(t0){var u0=[];return s0.Subscribe(function(v0){u0.push(v0);if(u0.leng
 th>r0)u0.shift();},function(v0){t0.OnError(v0);},function(){while(u0.length>0)t0.OnNext(u0.shift());t0.OnCompleted();});});}};var H=D.Merge=function(r0,s0){if(s0===a)s0=z;return J(r0,s0).MergeObservable();};var I=D.Concat=function(r0,s0){if(s0===a)s0=z;return E(function(t0){var u0=new o();var v0=0;var w0=s0.ScheduleRecursive(function(x0){if(v0<r0.length){var y0=r0[v0];v0++;var z0=new o();u0.Replace(z0);z0.Replace(y0.Subscribe(function(A0){t0.OnNext(A0);},function(A0){t0.OnError(A0);},x0));}else t0.OnCompleted();});return new n(u0,w0);});};var J=D.FromArray=function(r0,s0){if(s0===a)s0=z;return E(function(t0){var u0=0;return s0.ScheduleRecursive(function(v0){if(u0<r0.length){t0.OnNext(r0[u0++]);v0();}else t0.OnCompleted();});});};var K=D.Return=function(r0,s0){if(s0===a)s0=z;return E(function(t0){return s0.Schedule(function(){t0.OnNext(r0);t0.OnCompleted();});});};var L=D.Throw=function(r0,s0){if(s0===a)s0=z;return E(function(t0){return s0.Schedule(function(){t0.OnError(r0);});});};v
 ar M=D.Never=function(){return E(function(r0){return j;});};var N=D.Empty=function(r0){if(r0===a)r0=z;return E(function(s0){return r0.Schedule(function(){s0.OnCompleted();});});};var O=D.Defer=function(r0){return E(function(s0){var t0;try{t0=r0();}catch(u0){s0.OnError(u0);return j;}return t0.Subscribe(s0);});};var P=D.Catch=function(r0,s0){if(s0===a)s0=z;return E(function(t0){var u0=new o();var v0=0;var w0=s0.ScheduleRecursive(function(x0){var y0=r0[v0];v0++;var z0=new o();u0.Replace(z0);z0.Replace(y0.Subscribe(function(A0){t0.OnNext(A0);},function(A0){if(v0<r0.length)x0(); else t0.OnError(A0);},function(){t0.OnCompleted();}));});return new n(u0,w0);});};var Q=D.Using=function(r0,s0){return E(function(t0){var u0;var v0=j;try{var w0=r0();if(w0!==a)v0=w0;u0=s0(w0);}catch(x0){return new n(Throw(x0).Subscribe(t0),v0);}return new n(u0.Subscribe(t0),v0);});};var R=D.Range=function(r0,s0,t0){if(t0===a)t0=z;var u0=r0+s0-1;return T(r0,function(v0){return v0<=u0;},function(v0){return v0+1;},h
 ,t0);};var S=D.Repeat=function(r0,s0,t0){if(t0===a)t0=z;if(s0===a)s0=-1;var u0=s0;return E(function(v0){return t0.ScheduleRecursive(function(w0){v0.OnNext(r0);if(u0>0){u0--;if(u0==0){v0.OnCompleted();return;}}w0();});});};var T=D.Generate=function(r0,s0,t0,u0,v0){if(v0===a)v0=z;return E(function(w0){var x0=r0;var y0=true;return v0.ScheduleRecursive(function(z0){var A0=false;var B0;try{if(y0)y0=false; else x0=t0(x0);A0=s0(x0);if(A0)B0=u0(x0);}catch(C0){w0.OnError(C0);return;}if(A0){w0.OnNext(B0);z0();}else w0.OnCompleted();});});};var U=D.GenerateWithTime=function(r0,s0,t0,u0,v0,w0){if(w0===a)w0=A;return new E(function(x0){var y0=r0;var z0=true;var A0=false;var B0;var C0;return w0.ScheduleRecursiveWithTime(function(D0){if(A0)x0.OnNext(B0);try{if(z0)z0=false; else y0=t0(y0);A0=s0(y0);if(A0){B0=u0(y0);C0=v0(y0);}}catch(E0){x0.OnError(E0);return;}if(A0)D0(C0); else x0.OnCompleted();},0);});};var V=D.OnErrorResumeNext=function(r0,s0){if(s0===a)s0=z;return E(function(t0){var u0=new o();va
 r v0=0;var w0=s0.ScheduleRecursive(function(x0){if(v0<r0.length){var y0=r0[v0];v0++;var z0=new o();u0.Replace(z0);z0.Replace(y0.Subscribe(function(A0){t0.OnNext(A0);},x0,x0));}else t0.OnCompleted();});return new n(u0,w0);});};var W=D.Amb=function(){var r0=arguments;return E(function(s0){var t0=new n();var u0=new o();u0.Replace(t0);var v0=false;for(var w0=0;w0<r0.length;w0++){var x0=r0[w0];var y0=new o();var z0=new B(function(A0){if(!v0){t0.Remove(this.z,true);t0.Dispose();u0.Replace(this.z);v0=true;}s0.OnNext(A0);},function(A0){s0.OnError(A0);u0.Dispose();},function(){s0.OnCompleted();u0.Dispose();});z0.z=y0;y0.Replace(x0.Subscribe(z0));t0.Add(y0);}return u0;});};var X=D.ForkJoin=function(){var r0=arguments;return E(function(s0){var t0=[];var u0=[];var v0=[];var w0=new n();for(var x0=0;x0<r0.length;x0++) (function(y0){w0.Add(r0[y0].Subscribe(function(z0){t0[y0]=true;v0[y0]=z0;},function(z0){s0.OnError(z0);},function(z0){if(!t0[y0]){s0.OnCompleted();v0=a;t0=a;return;}u0[y0]=true;var 
 A0=true;for(var B0=0;B0<r0.length;B0++){if(!u0[B0])A0=false;}if(A0){s0.OnNext(v0);s0.OnCompleted();v0=a;u0=a;t0=a;}}));})(x0);return w0;});};var Y=D.Interval=function(r0,s0){return a0(r0,r0,s0);};var Z=function(r0){return Math.max(0,r0);};var a0=D.Timer=function(r0,s0,t0){if(t0===a)t0=A;if(r0===a)return M();if(r0 instanceof Date)return O(function(){return D.Timer(r0-new Date(),s0,t0);});var u0=Z(r0);if(s0===a)return E(function(w0){return t0.ScheduleWithTime(function(){w0.OnNext(0);w0.OnCompleted();},u0);});var v0=Z(s0);return E(function(w0){var x0=0;return t0.ScheduleRecursiveWithTime(function(y0){w0.OnNext(x0++);y0(v0);},u0);});};var b0=D.While=function(r0,s0){return E(function(t0){var u0=new o();var v0=new n(u0);v0.Add(z.ScheduleRecursive(function(w0){var x0;try{x0=r0();}catch(y0){t0.OnError(y0);return;}if(x0)u0.Replace(s0.Subscribe(function(y0){t0.OnNext(y0);},function(y0){t0.OnError(y0);},function(){w0();})); else t0.OnCompleted();}));return v0;});};var c0=D.If=function(r0,s0,t0
 ){if(t0===a)t0=N();return O(function(){return r0()?s0:t0;});};var d0=D.DoWhile=function(r0,s0){return I([r0,b0(s0,r0)]);};var e0=D.Case=function(r0,s0,t0,u0){if(u0===a)u0=z;if(t0===a)t0=N(u0);return O(function(){var v0=s0[r0()];if(v0===a)v0=t0;return v0;});};var f0=D.For=function(r0,s0){return E(function(t0){var u0=new n();var v0=0;u0.Add(z.ScheduleRecursive(function(w0){if(v0<r0.length){var x0;try{x0=s0(r0[v0]);}catch(y0){t0.OnError(y0);return;}u0.Add(x0.Subscribe(function(y0){t0.OnNext(y0);},function(y0){t0.OnError(y0);},function(){v0++;w0();}));}else t0.OnCompleted();}));return u0;});};var g0=D.Let=function(r0,s0){return O(function(){return s0(r0);});};var h0=b.Notification=function(r0,s0){this.Kind=r0;this.Value=s0;this.toString=function(){return this.Kind+": "+this.Value;};this.Accept=function(t0){switch(this.Kind){case "N":t0.OnNext(this.Value);break;case "E":t0.OnError(this.Value);break;case "C":t0.OnCompleted();break;}return j;};this.w=function(t0){var u0=this.Accept(t0);if(
 r0=="N")t0.OnCompleted();return u0;};};h0.prototype=new D;var i0=b.Subject=function(){var r0=new q();var s0=false;this.OnNext=function(t0){if(!s0){var u0=r0.ToArray();for(var v0=0;v0<u0.length;v0++){var w0=u0[v0];w0.OnNext(t0);}}};this.OnError=function(t0){if(!s0){var u0=r0.ToArray();for(var v0=0;v0<u0.length;v0++){var w0=u0[v0];w0.OnError(t0);}s0=true;r0.Clear();}};this.OnCompleted=function(){if(!s0){var t0=r0.ToArray();for(var u0=0;u0<t0.length;u0++){var v0=t0[u0];v0.OnCompleted();}s0=true;r0.Clear();}};this.w=function(t0){if(!s0){r0.Add(t0);return i(function(){r0.Remove(t0);});}else return j;};};i0.prototype=new D;for(var j0 in B.prototype) i0.prototype[j0]=B.prototype[j0];var k0=b.AsyncSubject=function(r0){var s0=new q();var t0;var u0=false;if(r0===a)r0=z;this.OnNext=function(v0){if(!u0)t0=new h0("N",v0);};this.OnError=function(v0){if(!u0){t0=new h0("E",v0);var w0=s0.ToArray();for(var x0=0;x0<w0.length;x0++){var y0=w0[x0];if(y0!==a)y0.OnError(v0);}u0=true;s0.Clear();}};this.OnCo
 mpleted=function(){if(!u0){if(t0===a)t0=new h0("C");var v0=s0.ToArray();for(var w0=0;w0<v0.length;w0++){var x0=v0[w0];if(x0!==a)t0.w(x0);}u0=true;s0.Clear();}};this.w=function(v0){if(!u0){s0.Add(v0);return i(function(){s0.Remove(v0);});}else return r0.Schedule(function(){t0.w(v0);});};};k0.prototype=new i0;var l0=b.BehaviorSubject=function(r0,s0){var t0=new m0(1,-1,s0);t0.OnNext(r0);return t0;};var m0=b.ReplaySubject=function(r0,s0,t0){var u0=new q();var v0=new q();var w0=false;if(t0===a)t0=v;var x0=s0>0;var y0=function(z0,A0){v0.Add({Value:new h0(z0,A0),Timestamp:t0.Now()});};this.A=function(){if(r0!==a)while(v0.GetCount()>r0)v0.RemoveAt(0);if(x0)while(v0.GetCount()>0&&t0.Now()-v0.GetItem(0).Timestamp>s0)v0.RemoveAt(0);};this.OnNext=function(z0){if(!w0){var A0=u0.ToArray();for(var B0=0;B0<A0.length;B0++){var C0=A0[B0];C0.OnNext(z0);}y0("N",z0);}};this.OnError=function(z0){if(!w0){var A0=u0.ToArray();for(var B0=0;B0<A0.length;B0++){var C0=A0[B0];C0.OnError(z0);}w0=true;u0.Clear();y0
 ("E",z0);}};this.OnCompleted=function(){if(!w0){var z0=u0.ToArray();for(var A0=0;A0<z0.length;A0++){var B0=z0[A0];B0.OnCompleted();}w0=true;u0.Clear();y0("C");}};this.w=function(z0){var A0=new n0(this,z0);var B0=new n(A0);var C0=this;B0.Add(t0.Schedule(function(){if(!A0.B){C0.A();for(var D0=0;D0<v0.GetCount();D0++) v0.GetItem(D0).Value.Accept(z0);u0.Add(z0);A0.C=true;}}));return B0;};this.D=function(z0){u0.Remove(z0);};};m0.prototype=new i0;var n0=function(r0,s0){this.E=r0;this.F=s0;this.C=false;this.B=false;this.Dispose=function(){if(this.C)this.E.D(this.F);this.B=true;};};var o0=D.ToAsync=function(r0,s0){if(s0===a)s0=A;return function(){var t0=new k0(s0);var u0=function(){var x0;try{x0=r0.apply(this,arguments);}catch(y0){t0.OnError(y0);return;}t0.OnNext(x0);t0.OnCompleted();};var v0=this;var w0=p(arguments);s0.Schedule(function(){u0.apply(v0,w0);});return t0;};};var p0=D.Start=function(r0,s0,t0,u0){if(t0===a)t0=[];return o0(r0,u0).apply(s0,t0);};var q0=b.ConnectableObservable=func
 tion(r0,s0){if(s0===a)s0=new i0();this.E=s0;this.G=r0;this.H=false;this.Connect=function(){var t0;var u0=false;if(!this.H){this.H=true;var v0=this;t0=new n(i(function(){v0.H=false;}));this.I=t0;t0.Add(r0.Subscribe(this.E));}return this.I;};this.w=function(t0){return this.E.Subscribe(t0);};this.RefCount=function(){var t0=0;var u0=this;var v0;return F(function(w0){var x0=false;t0++;x0=t0==1;var y0=u0.Subscribe(w0);if(x0)v0=u0.Connect();return function(){y0.Dispose();t0--;if(t0==0)v0.Dispose();};});};};q0.prototype=new D;})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/datajs-cache-large-collection-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/datajs-cache-large-collection-functional-tests.html b/JSLib/tests/datajs-cache-large-collection-functional-tests.html
new file mode 100644
index 0000000..bf5cad3
--- /dev/null
+++ b/JSLib/tests/datajs-cache-large-collection-functional-tests.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>datajs.cache and datajs.store full local store 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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+    
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="common/CacheOracle.js"></script>
+    <script type="text/javascript" src="common/ObservableHttpClient.js"></script>
+    <script type="text/javascript" src="common/ODataReadOracle.js"></script>
+    <script type="text/javascript" src="datajs-cache-large-collection-functional-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">datajs.cache and datajs.store full local store tests</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/datajs-cache-large-collection-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/datajs-cache-large-collection-functional-tests.js b/JSLib/tests/datajs-cache-large-collection-functional-tests.js
new file mode 100644
index 0000000..fd4e826
--- /dev/null
+++ b/JSLib/tests/datajs-cache-large-collection-functional-tests.js
@@ -0,0 +1,170 @@
+/// <reference path="../src/datajs.js" />
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/cache.js" />
+/// <reference path="common/djstest.js" />
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;q=0.1";
+    var largeCollectionFeed = "./endpoints/LargeCollectionService.svc/Customers";
+    var itemsInCollection = 2 * 1024 * 1024;
+
+    var cleanDomStorage = function (done) {
+        /// <summary>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.</summary>
+        /// <param name="done" type="Function">Function to be called after DOM storage is cleared.</param>
+        if (window.localStorage) {
+            window.localStorage.clear();
+        }
+        done();
+    };
+
+    var cleanIndexedDb = function (done) {
+        /// <summary>Cleans all the data saved in the browser's IndexedDb Storage.</summary>
+        /// <param name="done" type="Function">Function to be called after DOM storage is cleared.</param>
+        var caches = this.caches;
+
+        djstest.cleanStoreOnIndexedDb(caches, done);
+    };
+
+    var makeUnexpectedErrorHandler = function () {
+        return function (err) {
+            djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        };
+    };
+
+    var storageMechanisms = {
+        indexeddb: { cleanup: cleanIndexedDb },
+        dom: { cleanup: cleanDomStorage }
+    };
+
+    var cleanupAllStorage = function(done) {
+        /// <summary>Cleans up all available storage mechanisms in the browser.</summary>
+        /// <param name="done" type="Function">Function to be called by each cleanup function after storage is cleared.</param>
+        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);
+    };
+
+
+    module("Functional", {
+        setup: function () {
+            this.observableHttpClient = new ObservableHttpClient();
+            OData.defaultHttpClient = this.observableHttpClient;
+            this.caches = [];
+            var that = this;
+
+            djstest.wait(function (done) {
+                cleanupAllStorage.call(that, done);
+            });
+        },
+
+        teardown: function () {
+            OData.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(["dom", "indexeddb"], function (_, mechanism) {
+        if (CacheOracle.isMechanismAvailable(mechanism)) {
+            $.each([-1, 10 * 1024 * 1024, 1024 * 10248], function (_, cacheSize) {
+                var prefetchParameters = { mechanism: mechanism, feed: largeCollectionFeed, skip: 0, take: 5, pageSize: 1024, prefetchSize: -1, cacheSize: cacheSize };
+                djstest.addTest(function (params) {
+
+                    djstest.assertsExpected(3);
+                    var options = { name: "cache" + new Date().valueOf(), source: params.feed, pageSize: params.pageSize, prefetchSize: params.prefetchSize,
+                        mechanism: params.mechanism, cacheSize: params.cacheSize
+                    };
+
+                    var cache = datajs.createDataCache(options);
+                    this.caches.push({ name: options.name,
+                        cache: cache
+                    });
+
+                    cache.onidle = function () {
+                        djstest.assert(true, "onidle Called");
+                        djstest.done();
+                    };
+
+                    var cacheOracle = new CacheOracle(params.feed, params.pageSize, itemsInCollection);
+                    var session = this.observableHttpClient.newSession();
+
+                    cache.readRange(params.skip, params.take).then(function (data) {
+                        var expectedRangeUrl = params.feed + "?$skip=" + params.skip + "&$top=" + params.take;
+                        cacheOracle.verifyRequests(session.requests, session.responses, params.skip, params.take, "largeCollection requests with prefetch", false, true);
+                        window.ODataReadOracle.readJsonAcrossServerPages(expectedRangeUrl, function (expectedData) {
+                            djstest.assertAreEqualDeep(data, expectedData, "Verify response data");
+                        });
+                    }, function (err) {
+                        makeUnexpectedErrorHandler(err)();
+                    });
+                }, "readRange and prefetch all to fill store on " + prefetchParameters.mechanism + " with cacheSize=" + prefetchParameters.cacheSize, prefetchParameters, 600000);
+
+                $.each([500, 1024 * 10 /*Test reduced from 100 to 10 to work around slow running script error in IE8 and Safari (bug 2200)*/], function (_, pageSize) {
+                    var largeReadParameters = { mechanism: mechanism, feed: largeCollectionFeed, skip: 0, take: 1024, pageSize: pageSize, prefetchSize: 0, cacheSize: cacheSize };
+                    djstest.addTest(function (params) {
+
+                        djstest.assertsExpected(2);
+                        var options = { name: "cache" + new Date().valueOf(), source: params.feed, pageSize: params.pageSize, prefetchSize: params.prefetchSize,
+                            mechanism: params.mechanism, cacheSize: params.cacheSize
+                        };
+
+                        var cache = datajs.createDataCache(options);
+                        this.caches.push({ name: options.name, cache: cache });
+
+                        var cacheOracle = new CacheOracle(params.feed, params.pageSize, itemsInCollection);
+                        var session = this.observableHttpClient.newSession();
+
+                        cache.readRange(params.skip, params.take).then(function (data) {
+                            var expectedRangeUrl = params.feed + "?$skip=" + params.skip + "&$top=" + params.take;
+                            cacheOracle.verifyRequests(session.requests, session.responses, params.skip, params.take, "largeCollection requests without prefetch", false, false);
+                            window.ODataReadOracle.readJsonAcrossServerPages(expectedRangeUrl, function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify response data");
+                                djstest.done();
+                            });
+                        }, function (err) {
+                            makeUnexpectedErrorHandler(err)();
+                            djstest.done();
+                        });
+                    }, "readRange of skip=" + largeReadParameters.skip + " take=" + largeReadParameters.take + " cacheSize=" + largeReadParameters.cacheSize + " and pageSize=" + largeReadParameters.pageSize +
+                        " to fill store on " + largeReadParameters.mechanism, largeReadParameters, 600000);
+                });
+            });
+        }
+    });
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/datajs-cache-long-haul-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/datajs-cache-long-haul-tests.html b/JSLib/tests/datajs-cache-long-haul-tests.html
new file mode 100644
index 0000000..39eda48
--- /dev/null
+++ b/JSLib/tests/datajs-cache-long-haul-tests.html
@@ -0,0 +1,192 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>OData Long Haul 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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="common/Instrument.js"></script>
+    <script type="text/javascript">
+        var cache;
+        var clearBetweenReads;
+        var currentRunTimeMS;
+        var failureCount;
+        var hourMS = 60 * 60 * 1000;
+        var goalRunTimeMS = hourMS;
+        var isTotalIncreaseHigh;
+        var itemsInCollection = 16;
+        var iterationsRun;
+        var memoryDelta = 5000000;
+        var getBrowserMemorySize;
+        var startMemorySize;
+        var startTimeMs = 0;
+        var testFeed = "http://odata.netflix.com/Catalog/Titles";
+
+        var makeUnexpectedErrorHandler = function () {
+            return function (err) {
+                failureCount++;
+                if (failureCount < 50) {
+                    $("#errors").append("<p>Iteration: " + iterationsRun + " Error reading cache: " + err.message + "<p>");
+                }
+            };
+        };
+
+        var checkMemoryIncrease = function (memorySizeBefore) {
+            getBrowserMemorySize(function (memorySizeAfter) {
+                var incrementalIncreaseInMemory = memorySizeAfter - memorySizeBefore;
+                var totalIncreaseInMemory = memorySizeAfter - startMemorySize;
+                if (incrementalIncreaseInMemory > memoryDelta) {
+                    failureCount++;
+                    if (failureCount < 50) {
+                        $("#errors").append("<p>Iteration: " + iterationsRun + " Memory usage increase in read: " + incrementalIncreaseInMemory + "<p>");
+                    }
+                }
+
+                if (totalIncreaseInMemory > memoryDelta && !isTotalIncreaseHigh) {
+                    isTotalIncreaseHigh = true;
+                    failureCount++;
+                    if (failureCount < 50) {
+                        $("#errors").append("<p>Iteration: " + iterationsRun + " Total memory usage increase over duration: " + totalIncreaseInMemory + "<p>");
+                    }
+                }
+
+                iterationsRun++;
+                nextIteration();
+            });
+        };
+
+        var readRangePrefetchTest = function () {
+            var readPage = 6;
+
+            getBrowserMemorySize(function (memorySizeBefore) {
+                callReadRangeRepeatedly(0, readPage, function () { checkMemoryIncrease(memorySizeBefore) });
+            });
+        };
+
+        var callReadRangeRepeatedly = function (index, count, done) {
+            /// <summary>Calls readRange over the whole collection and done when all items have been read.</summary>
+            /// <param name="index" type="Integer">Index to start the read.</param>
+            /// <param name="count" type="String">The count of each readRange.</param>
+            /// <param name="done" type="Function">Function to be called when all items in collection have been read.</param>
+            var cacheRead = function () {
+                cache.readRange(index, count).then(function () {
+                    if (index < itemsInCollection) {
+                        index += count;
+                        callReadRangeRepeatedly(index, count, done);
+                    }
+                    else {
+                        done();
+                    }
+                }, makeUnexpectedErrorHandler(cache));
+            };
+
+            if (clearBetweenReads) {
+                cache.clear().then(cacheRead(), makeUnexpectedErrorHandler(cache));
+            }
+            else {
+                cacheRead(); 
+            }
+        }
+
+        function startTest() {
+            var prefetchSize =
+                $('input[name=PrefetchEnabled]').attr('checked') ? -1 : 0;
+            clearBetweenReads = $('input[name=ClearBetweenReads]').attr('checked');
+            var inputHours = parseFloat($('#time').val());
+
+            if (inputHours !== undefined && typeof inputHours === "number" && !isNaN(inputHours) && isFinite(inputHours) && inputHours > 0) {
+                goalRunTimeMS = hourMS * inputHours;
+            }
+            OData.defaultHttpClient.enableJsonpCallback = true;
+            cache = datajs.createDataCache({ name: "cache" + new Date().valueOf(), source: testFeed, pageSize: 5, prefetchSize: prefetchSize });
+            failureCount = 0;
+            iterationsRun = 0;
+            currentRunTimeMS = 0;
+            isTotalIncreaseHigh = false;
+            $("#errors").empty();
+            getBrowserMemorySize = Instrument.getBrowserMemorySize;
+            getBrowserMemorySize(function (memorySizeStart) {
+                startMemorySize = memorySizeStart;
+                $("#starting-memory").text("Starting memory size: " + startMemorySize);
+            });
+            startTimeMs = new Date().getTime();
+
+            nextIteration();
+        }
+
+        function nextIteration() {
+            currentRunTimeMS = new Date().getTime() - startTimeMs;
+            if (currentRunTimeMS > goalRunTimeMS) {
+                getBrowserMemorySize(function (memorySizeAfter) {
+                    $("#stress-status").text("Tests complete. Iterations: " + iterationsRun + " Failures: " + failureCount +
+                    " Start memory: " + startMemorySize + " End memory: " + memorySizeAfter);
+                });
+
+                cache.clear();
+                return;
+            }
+
+            if (iterationsRun % 100 === 0) {
+                getBrowserMemorySize(function (memorySizeAfter) {
+                    var text = "Running tests (iterationsRun=" + iterationsRun;
+                    text += ", Time run: " + currentRunTimeMS + "ms";
+                    text += ", Remaining time: " + (goalRunTimeMS - currentRunTimeMS) + "ms";
+                    text += "). Failures: " + failureCount;
+                    text += " Current memory size: " + memorySizeAfter;
+                    $("#stress-status").text(text);
+                });
+            }
+
+            readRangePrefetchTest();
+        }
+
+        $(document).ready(function () {
+            $("#start-button").click(startTest);
+        });
+    </script>
+</head>
+<body>
+    <h1>
+        OData Long Haul</h1>
+    <p>
+        Repeatedly runs an action and checks the memory usage.
+    </p>
+    <p>
+        <input name="time" id="time" type="text" />Length of time to run test</p>
+    <p>
+        <input type="checkbox" name="PrefetchEnabled" value="false" />Prefetch Enabled</p>
+    <p>
+        <input type="checkbox" name="ClearBetweenReads" value="false" />Clear Between Reads</p>
+    <button id='start-button'>
+        Start</button>
+    <p id='errors'>
+    </p>
+    <p id='stress-status'>
+    </p>
+    <p id='starting-memory'>
+    </p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/datajs-startup-perf-test.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/datajs-startup-perf-test.html b/JSLib/tests/datajs-startup-perf-test.html
new file mode 100644
index 0000000..465ee01
--- /dev/null
+++ b/JSLib/tests/datajs-startup-perf-test.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<!--
+Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<head>
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+    <title>datajs startup perf test</title>
+
+    <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="common/djstest.js"></script>
+    <script type="text/javascript" src="common/Instrument.js"></script>
+
+    <script type="text/javascript">
+        $(window).load(function () {
+            djstest.addTest(function startupTimeAndMemory() {
+                var durationLimit = 500;
+                var memorySizeDeltaLimit = 5000000;
+
+                var filename = "datajs-1.1.0.min.js";
+                var getBrowserMemorySize = Instrument.getBrowserMemorySize;
+
+                $.ajax({
+                    url: "../src/" + filename,
+                    dataType: "text",
+                    success: function (script) {
+                        getBrowserMemorySize(function (memorySizeBefore) {
+                            var duration = new Date();
+                            eval(script);
+                            duration = new Date() - duration;
+                            getBrowserMemorySize(function (memorySizeAfter) {
+                                var memorySizeDelta = memorySizeAfter - memorySizeBefore;
+                                djstest.assert(duration < durationLimit, duration + " ms (limit " + durationLimit + " ms)");
+                                djstest.assert(memorySizeDelta < memorySizeDeltaLimit,
+                                    memorySizeDelta + " bytes (limit " + memorySizeDeltaLimit + " bytes, initial " + memorySizeBefore + " bytes)");
+
+                                djstest.done();
+                            });
+                        });
+                    },
+                    error: function () {
+                        // See if we are running the dev build
+                        $.ajax({
+                            url: "../src/odata.js",
+                            dataType: "text",
+                            success: function () {
+                                djstest.pass("Running on dev build, no measurement taken");
+                                djstest.done();
+                            },
+                            error: function (jqXHR, textStatus, errorThrown) {
+                                djstest.fail("Request failed: " + jqXHR.responseText);
+                                djstest.done();
+                            }
+                        });
+                    }
+                });
+            });
+        });
+    </script>
+</head>
+<body>
+    <h1 id="qunit-header">datajs startup perf test</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests">
+    </ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/endpoints/BasicAuthDataService.svc
----------------------------------------------------------------------
diff --git a/JSLib/tests/endpoints/BasicAuthDataService.svc b/JSLib/tests/endpoints/BasicAuthDataService.svc
new file mode 100644
index 0000000..66dcb1f
--- /dev/null
+++ b/JSLib/tests/endpoints/BasicAuthDataService.svc
@@ -0,0 +1,108 @@
+<%@ ServiceHost Language="C#" Factory="Microsoft.OData.Service.DataServiceHostFactory, Microsoft.OData.Service, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.BasicAuthDataService" %>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using Microsoft.OData.Service;
+    using Microsoft.OData.Service.Common;
+    using System.Linq;
+    using System.ServiceModel;
+    using System.ServiceModel.Web;
+    using System.Text;
+    using System.Web;
+
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    public class BasicAuthDataService : DataService<BasicAuthDataSource>
+    {
+        const string Username = "djsUser";
+        const string Password = "djsPassword";
+        
+        // This method is called only once to initialize service-wide policies.
+        public static void InitializeService(DataServiceConfiguration config)
+        {
+            config.SetEntitySetAccessRule("*", EntitySetRights.All);
+            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
+            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V4;
+            config.UseVerboseErrors = true;
+        }
+
+        public BasicAuthDataService()
+            : base()
+        {
+            this.ProcessingPipeline.ProcessingRequest += OnRequest;
+        }
+
+        [WebInvoke]
+        public void ResetData()
+        {
+            this.CurrentDataSource.ResetData();
+        }
+
+        private static void UnauthorizedRequest(DataServiceOperationContext context)
+        {
+            context.ResponseHeaders["WWW-Authenticate"] = "Basic realm=\"DataJS.Tests\"";
+            throw new DataServiceException(401, "401 Unauthorized");
+        }
+
+        private void OnRequest(object sender, DataServiceProcessingPipelineEventArgs e)
+        {
+            string authHeader = e.OperationContext.RequestHeaders["Authorization"];
+            
+            // Validate the Authorization header
+            if (authHeader == null || !authHeader.StartsWith("Basic"))
+            {
+                UnauthorizedRequest(e.OperationContext);
+            }
+
+            // Decode the username and password from the header
+            string base64Credentials = authHeader.Substring(6);
+            string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(':');
+            if (credentials.Length != 2 || !(credentials[0].Equals(Username) && credentials[1].Equals(Password)))
+            {
+                UnauthorizedRequest(e.OperationContext);
+            }
+        }
+    }
+
+    public class BasicAuthDataSource : ReflectionDataContext, IUpdatable
+    {
+        private static bool dataInitialized;
+
+        public IQueryable<Customer> Customers
+        {
+            get { return this.GetResourceSetEntities<Customer>("Customers").AsQueryable(); }
+        }
+
+        public void ResetData()
+        {
+            this.ClearData();
+
+            IList<Customer> customers = this.GetResourceSetEntities<Customer>("Customers");
+            foreach (int i in Enumerable.Range(1, 16))
+            {
+                customers.Add(new Customer()
+                {
+                    ID = i,
+                    Name = "Customer " + i
+                });
+            }
+        }
+
+        protected override void EnsureDataIsInitialized()
+        {
+            if (!dataInitialized)
+            {
+                this.ResetData();
+                dataInitialized = true;
+            }
+        }
+    }
+
+    public class Customer
+    {
+        public int ID { get; set; }
+        public string Name { get; set; }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/endpoints/CustomAnnotations.xml
----------------------------------------------------------------------
diff --git a/JSLib/tests/endpoints/CustomAnnotations.xml b/JSLib/tests/endpoints/CustomAnnotations.xml
new file mode 100644
index 0000000..622769f
--- /dev/null
+++ b/JSLib/tests/endpoints/CustomAnnotations.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">
+  <edmx:DataServices m:DataServiceVersion="2.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
+    <Schema xmlns="http://schemas.microsoft.com/ado/2007/05/edm" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:mcns="http://MyCustomNamespace.com"
+             Namespace="DataJS.Tests">
+      <EntityType Name="MappedEntry" mcns:MyCustomAnnotation="My custom attribute.">
+        <Key mcns:MyCustomAnnotation="//">
+          <PropertyRef Name="ID" mcns:MyCustomAnnotation="  "/>
+        </Key>
+        <Property Name="ID" Nullable="false" Type="Edm.Int32"/>
+        <Property Name="UnmappedField" Nullable="true" Type="Edm.String"/>
+        <Property Name="Author" Nullable="false" Type="DataJS.Tests.Author" m:FC_KeepInContent_5="false" m:FC_SourcePath_5="Contributor/Name" m:FC_ContentKind_5="text" m:FC_TargetPath_5="SyndicationContributorName" m:FC_KeepInContent_4="false" m:FC_SourcePath_4="Contributor/Email" m:FC_ContentKind_4="text" m:FC_TargetPath_4="SyndicationContributorEmail" m:FC_KeepInContent_3="false" m:FC_SourcePath_3="Uri" m:FC_ContentKind_3="text" m:FC_TargetPath_3="SyndicationAuthorUri" m:FC_KeepInContent_2="false" m:FC_SourcePath_2="Name" m:FC_ContentKind_2="text" m:FC_TargetPath_2="SyndicationAuthorName" m:FC_KeepInContent_1="false" m:FC_SourcePath_1="Email" m:FC_ContentKind_1="text" m:FC_TargetPath_1="SyndicationAuthorEmail" m:FC_KeepInContent="false" m:FC_SourcePath="Contributor/Uri" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationContributorUri"/>
+        <Property Name="Published" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationPublished"/>
+        <Property Name="Rights" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationRights"/>
+        <Property Name="Summary" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_ContentKind="xhtml" m:FC_TargetPath="SyndicationSummary"/>
+        <Property Name="Title" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_ContentKind="html" m:FC_TargetPath="SyndicationTitle"/>
+        <Property Name="Updated" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationUpdated"/>
+        <Property Name="CustomElement" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="customElement" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.org/dummy"/>
+        <Property Name="CustomAttribute" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="customElement/@customAttribute" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.org/dummy"/>
+        <Property Name="NestedElement1" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="commonElement/nestedElement1" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="NestedElement2" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="commonElement/nestedElement2" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="CommonAttribute1" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="commonElement/@commonAttribute1" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="CommonAttribute2" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="commonElement/@commonAttribute2" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="Location" Nullable="false" Type="DataJS.Tests.Location" m:FC_KeepInContent_1="false" m:FC_SourcePath_1="Long" m:FC_TargetPath_1="long" m:FC_KeepInContent="false" m:FC_SourcePath="Lat" m:FC_TargetPath="lat" m:FC_NsPrefix="geo" m:FC_NsUri="http://www.georss.org/georss" m:FC_NsPrefix_1="geo" m:FC_NsUri_1="http://www.georss.org/georss"/>
+      </EntityType>
+      <ComplexType Name="Author" mcns:MyCustomAnnotation="">
+        <Property Name="Email" Nullable="true" Type="Edm.String" mcns:MyCustomAnnotation=""/>
+        <Property Name="Name" Nullable="true" Type="Edm.String"/>
+        <Property Name="Uri" Nullable="true" Type="Edm.String"/>
+        <Property Name="Contributor" Nullable="false" Type="DataJS.Tests.Contributor"/>
+      </ComplexType>
+      <ComplexType Name="Contributor">
+        <Property Name="Email" Nullable="true" Type="Edm.String" mcns:MyCustomAnnotation=" "/>
+        <Property Name="Name" Nullable="true" Type="Edm.String"/>
+        <Property Name="Uri" Nullable="true" Type="Edm.String" mcns:MyCustomAnnotation="true"/>
+      </ComplexType>
+      <ComplexType Name="Location">
+        <Property Name="Lat" Nullable="false" Type="Edm.Single" mcns:MyCustomAnnotation="27"/>
+        <Property Name="Long" Nullable="false" Type="Edm.Single"/>
+      </ComplexType>
+      <EntityType Name="ReplicatedEntry">
+        <Key>
+          <PropertyRef Name="ID"/>
+        </Key>
+        <Property Name="ID" Nullable="false" Type="Edm.Int32"/>
+        <Property Name="UnmappedField" Nullable="true" Type="Edm.String"/>
+        <Property Name="Author" Nullable="false" Type="DataJS.Tests.Author2" m:FC_KeepInContent_5="true" m:FC_SourcePath_5="Contributor/Uri" m:FC_ContentKind_5="text" m:FC_TargetPath_5="SyndicationContributorUri" m:FC_KeepInContent_4="true" m:FC_SourcePath_4="Contributor/Name" m:FC_ContentKind_4="text" m:FC_TargetPath_4="SyndicationContributorName" m:FC_KeepInContent_3="true" m:FC_SourcePath_3="Contributor/Email" m:FC_ContentKind_3="text" m:FC_TargetPath_3="SyndicationContributorEmail" m:FC_KeepInContent_2="true" m:FC_SourcePath_2="Uri" m:FC_ContentKind_2="text" m:FC_TargetPath_2="SyndicationAuthorUri" m:FC_KeepInContent_1="true" m:FC_SourcePath_1="Name" m:FC_ContentKind_1="text" m:FC_TargetPath_1="SyndicationAuthorName" m:FC_KeepInContent="true" m:FC_SourcePath="Email" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationAuthorEmail" mcns:MyCustomAnnotation="b>100/b>"/>
+        <Property Name="Published" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationPublished" mcns:MyCustomAnnotation=" . "/>
+        <Property Name="Rights" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationRights"/>
+        <Property Name="Summary" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_ContentKind="xhtml" m:FC_TargetPath="SyndicationSummary" mcns:MyCustomAnnotation="/Property"/>
+        <Property Name="Title" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_ContentKind="html" m:FC_TargetPath="SyndicationTitle"/>
+        <Property Name="Updated" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_ContentKind="text" m:FC_TargetPath="SyndicationUpdated"/>
+        <Property Name="CustomElement" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_TargetPath="customElement" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.org/dummy"/>
+        <Property Name="CustomAttribute" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_TargetPath="customElement/@customAttribute" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.org/dummy"/>
+        <Property Name="NestedElement1" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_TargetPath="commonElement/nestedElement1" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="NestedElement2" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_TargetPath="commonElement/nestedElement2" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="CommonAttribute1" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_TargetPath="commonElement/@commonAttribute1" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="CommonAttribute2" Nullable="true" Type="Edm.String" m:FC_KeepInContent="true" m:FC_TargetPath="commonElement/@commonAttribute2" m:FC_NsPrefix="pr" m:FC_NsUri="http://www.example.com/dummy"/>
+        <Property Name="Location" Nullable="false" Type="DataJS.Tests.Location2" m:FC_KeepInContent_1="true" m:FC_SourcePath_1="Long" m:FC_TargetPath_1="long" m:FC_KeepInContent="true" m:FC_SourcePath="Lat" m:FC_TargetPath="lat" m:FC_NsPrefix="geo" m:FC_NsUri="http://www.georss.org/georss" m:FC_NsPrefix_1="geo" m:FC_NsUri_1="http://www.georss.org/georss"/>
+      </EntityType>
+      <ComplexType Name="Author2">
+        <Property Name="Email" Nullable="true" Type="Edm.String"/>
+        <Property Name="Name" Nullable="true" Type="Edm.String"/>
+        <Property Name="Uri" Nullable="true" Type="Edm.String"/>
+        <Property Name="Contributor" Nullable="false" Type="DataJS.Tests.Contributor2"/>
+      </ComplexType>
+      <ComplexType Name="Contributor2">
+        <Property Name="Email" Nullable="true" Type="Edm.String"/>
+        <Property Name="Name" Nullable="true" Type="Edm.String"/>
+        <Property Name="Uri" Nullable="true" Type="Edm.String"/>
+      </ComplexType>
+      <ComplexType Name="Location2">
+        <Property Name="Lat" Nullable="false" Type="Edm.Single"/>
+        <Property Name="Long" Nullable="false" Type="Edm.Single"/>
+      </ComplexType>
+      <EntityType Name="DerivedEntry" m:FC_KeepInContent="false" m:FC_SourcePath="MappedInDerivedField" m:FC_TargetPath="mappedField/@mappedInDerived" m:FC_NsPrefix="pre" m:FC_NsUri="http://www.example.com/dummy" BaseType="DataJS.Tests.BaseEntry">
+        <Property Name="UnmappedConcreteField" Nullable="true" Type="Edm.String"/>
+        <Property Name="MappedConcreteField" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="mappedField/@mappedConcrete" m:FC_NsPrefix="pre" m:FC_NsUri="http://www.example.com/dummy"/>
+      </EntityType>
+      <EntityType Name="BaseEntry">
+        <Key>
+          <PropertyRef Name="ID"/>
+        </Key>
+        <Property Name="ID" Nullable="false" Type="Edm.Int32"/>
+        <Property Name="UnmappedField" Nullable="true" Type="Edm.String"/>
+        <Property Name="MappedInDerivedField" Nullable="true" Type="Edm.String" mcns:MyCustomAnnotation="false"/>
+        <Property Name="MappedField" Nullable="true" Type="Edm.String" m:FC_KeepInContent="false" m:FC_TargetPath="mappedField" m:FC_NsPrefix="pre" m:FC_NsUri="http://www.example.com/dummy"/>
+      </EntityType>
+      <EntityContainer Name="EpmDataSource" m:IsDefaultEntityContainer="true" mcns:MyCustomAnnotation="0">
+        <EntitySet Name="MappedEntries" EntityType="DataJS.Tests.MappedEntry"/>
+        <EntitySet Name="ReplicatedEntries" EntityType="DataJS.Tests.ReplicatedEntry"/>
+        <EntitySet Name="HierarchicalEntries" EntityType="DataJS.Tests.BaseEntry"/>
+        <FunctionImport Name="ResetData" m:HttpMethod="POST" mcns:MyCustomAnnotation="null"/>
+      </EntityContainer>
+      <mcns:EmptyElement></mcns:EmptyElement>
+      <mcns:ParentCustomElement mcns:MyCustomAnnotation="annotation" mcns:MySecondCustomAnnotation="annotation 2">
+        <mcns:CustomChildElement1>Custom metadata 1.</mcns:CustomChildElement1>
+        <mcns:CustomChildElement2>Custom metadata 2.</mcns:CustomChildElement2>
+      </mcns:ParentCustomElement>
+    </Schema>
+  </edmx:DataServices>
+</edmx:Edmx>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/endpoints/CustomDataService.svc
----------------------------------------------------------------------
diff --git a/JSLib/tests/endpoints/CustomDataService.svc b/JSLib/tests/endpoints/CustomDataService.svc
new file mode 100644
index 0000000..f30a261
--- /dev/null
+++ b/JSLib/tests/endpoints/CustomDataService.svc
@@ -0,0 +1,76 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebServiceHostFactory"
+    Service="DataJS.Tests.CustomDataService" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+using System.Collections;
+using System.IO;
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Web;
+
+    /// <summary>
+    /// Custom data service that does not use OData
+    /// </summary>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class CustomDataService
+    {
+        static readonly Item[] data = Enumerable.Range(0, 16).Select(i => new Item
+        {
+            ID = i,
+            Name = "Item " + i
+        }).ToArray();
+
+        // This uses the same URI template as OData so that the CacheOracle can be reused
+        [OperationContract]
+        [WebGet(UriTemplate = "ReadRange?$skip={skip}&$top={top}")]
+        public Stream ReadRange(int skip, int top)
+        {
+            IEnumerable selectData = data.Skip(skip).Take(top);
+            Dictionary<string, object> result = new Dictionary<string, object>();
+            List<Dictionary<string, string>> value = new List<Dictionary<string, string>>(); 
+            foreach (Item d in selectData)
+            {
+                Dictionary<string, string> item = new Dictionary<string, string>();
+                item.Add("ID", d.ID.ToString());
+                item.Add("Name", d.Name);
+                value.Add(item);
+            }
+            
+            result.Add("value", value);
+            return ReaderUtils.ConvertDictionarytoJsonlightStream(result);
+        }
+
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public int Count()
+        {
+            return data.Count();
+        }
+    }
+
+    public class Item
+    {
+        public int ID { get; set; }
+        public string Name { get; set; }
+    }
+}
\ No newline at end of file


[08/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/endpoints/EpmDataService.svc
----------------------------------------------------------------------
diff --git a/JSLib/tests/endpoints/EpmDataService.svc b/JSLib/tests/endpoints/EpmDataService.svc
new file mode 100644
index 0000000..ba90c23
--- /dev/null
+++ b/JSLib/tests/endpoints/EpmDataService.svc
@@ -0,0 +1,315 @@
+<%@ ServiceHost Language="C#" Factory="Microsoft.OData.Service.DataServiceHostFactory, Microsoft.OData.Service, Version=6.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.EpmDataService" %>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using Microsoft.OData.Service;
+    using Microsoft.OData.Service.Common;
+    using System.Linq;
+    using System.ServiceModel.Web;
+
+    /// <summary>
+    /// A data service that uses EPM
+    /// </summary>
+    [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    public class EpmDataService : DataService<EpmDataSource>
+    {
+        // This method is called only once to initialize service-wide policies.
+        public static void InitializeService(DataServiceConfiguration config)
+        {
+            config.SetEntitySetAccessRule("*", EntitySetRights.All);
+            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
+            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V4;
+            config.UseVerboseErrors = true;
+
+        }
+        
+        [WebInvoke]
+        public void ResetData()
+        {
+            this.CurrentDataSource.ResetData();
+        }
+    }
+
+    public class EpmDataSource : ReflectionDataContext, IUpdatable
+    {
+        private static bool dataInitialized;
+
+        public IQueryable<MappedEntry> MappedEntries
+        {
+            get { return this.GetResourceSetEntities<MappedEntry>("MappedEntries").AsQueryable(); }
+        }
+
+        public IQueryable<ReplicatedEntry> ReplicatedEntries
+        {
+            get { return this.GetResourceSetEntities<ReplicatedEntry>("ReplicatedEntries").AsQueryable(); }
+        }
+
+        public IQueryable<BaseEntry> HierarchicalEntries
+        {
+            get { return this.GetResourceSetEntities<BaseEntry>("HierarchicalEntries").AsQueryable(); }
+        }
+
+        public void ResetData()
+        {
+            this.ClearData();
+
+            MappedEntry[] mappedEntries = new MappedEntry[]
+            {
+                new MappedEntry
+                {
+                    ID = 0,
+                    UnmappedField = "Unmapped0",
+                    Author = new Author
+                    {
+                        Email = "AuthorEmail0",
+                        Name = "AuthorName0",
+                        Uri = "http://www.example.com/AuthorUri",
+                        Contributor = new Contributor
+                        {
+                            Email = "ContributorEmail0",
+                            Name = "ContributorName0",
+                            Uri = "http://www.example.com/ContributorUri",
+                        },
+                    },
+                    Published = "2000-01-01T00:00:00-08:00",
+                    Rights = "Rights0",
+                    Summary = "<xmlElement xmlns=\"http://www.example.org/dummy\" attr=\"value0\">Summary0</xmlElement>",
+                    Title = "Title<b>0</b>",
+                    Updated = "2000-01-01T00:00:00-08:00",
+                    CustomElement = "CustomElement0",
+                    CustomAttribute = "CustomAttribute0",
+                    NestedElement1 = "NestedElement1_0",
+                    NestedElement2 = "NestedElement2_0",
+                    CommonAttribute1 = "CommonAttribute1_0",
+                    CommonAttribute2 = "CommonAttribute2_0",
+                    Location = new Location
+                    {
+                        Lat = 3.14f,
+                        Long = 2.72f
+                    }
+                },
+                
+                new MappedEntry
+                {
+                    ID = 1,
+                    UnmappedField = null,
+                    Author = new Author
+                    {
+                        Email = null,
+                        Name = string.Empty,
+                        Uri = "http://www.example.com/AuthorUri1",
+                        Contributor = new Contributor
+                        {
+                            Email = null,
+                            Name = string.Empty,
+                            Uri = "http://www.example.com/ContributorUri1",
+                        },
+                    },
+                    Published = "2000-01-01T00:00:00-08:00",
+                    Rights = null,
+                    Summary = "",
+                    Title = "Title<i>1</i>",
+                    Updated = "2111-01-01T00:00:00-08:00",
+                    CustomElement = null,
+                    NestedElement1 = string.Empty,
+                    NestedElement2 = "NestedElement2_1",
+                    CustomAttribute = null,
+                    CommonAttribute1 = string.Empty,
+                    CommonAttribute2 = "CommonAttribute2_1",
+                    Location = new Location
+                    {
+                        Lat = float.MaxValue,
+                        Long = float.MinValue
+                    }
+                },
+                
+                new MappedEntry
+                {
+                    ID = 2,
+                    UnmappedField = "Unmapped2",
+                    Author = new Author
+                    {
+                        Email = "AuthorEmail2",
+                        Name = "AuthorName2",
+                        Uri = "http://www.example.com/AuthorUri2",
+                        Contributor = null
+                    },
+                    Published = "2000-01-01T00:00:00-08:00",
+                    Rights = "Rights2",
+                    Summary = "Summary2",
+                    Title = "Title2",
+                    Updated = "2000-01-01T00:00:00-08:00",
+                    CustomElement = "CustomElement2",
+                    CustomAttribute = "CustomAttribute2",
+                    NestedElement1 = "NestedElement1_2",
+                    NestedElement2 = "NestedElement2_2",
+                    CommonAttribute1 = "CommonAttribute1_2",
+                    CommonAttribute2 = "CommonAttribute2_2",
+                    Location = null
+                },
+            };
+            Array.ForEach(mappedEntries, (item) => this.GetResourceSetEntities<MappedEntry>("MappedEntries").Add(item));
+
+            Array.ForEach(mappedEntries, (item) => this.GetResourceSetEntities<ReplicatedEntry>("ReplicatedEntries").Add(new ReplicatedEntry
+            {
+                ID = item.ID,
+                UnmappedField = item.UnmappedField,
+                Author = item.Author == null ? null : new Author2
+                {
+                    Email = item.Author.Email,
+                    Name = item.Author.Name,
+                    Uri = item.Author.Uri,
+                    Contributor = item.Author.Contributor == null ? null : new Contributor2
+                    {
+                        Name = item.Author.Contributor.Name,
+                        Email = item.Author.Contributor.Email,
+                        Uri = item.Author.Contributor.Uri
+                    },
+                },
+                Published = item.Published,
+                Rights = item.Rights,
+                Summary = item.Summary,
+                Title = item.Title,
+                Updated = item.Updated,
+                CustomElement = item.CustomElement,
+                CustomAttribute = item.CustomAttribute,
+                NestedElement1 = item.NestedElement1,
+                NestedElement2 = item.NestedElement2,
+                CommonAttribute1 = item.CommonAttribute1,
+                CommonAttribute2 = item.CommonAttribute2,
+                Location = item.Location == null ? null : new Location2
+                {
+                    Lat = item.Location.Lat,
+                    Long = item.Location.Long
+                }
+            }));
+
+            BaseEntry[] hierarchicalEntries = new BaseEntry[]
+            {
+                new BaseEntry
+                {
+                    ID = 0,
+                    MappedField = "MappedField0",
+                    MappedInDerivedField = "MappedInDerivedField0",
+                    UnmappedField = "UnmappedField0"
+                },
+                new DerivedEntry
+                {
+                    ID = 1,
+                    MappedField = "MappedField1",
+                    MappedInDerivedField = "MappedInDerivedField1",
+                    UnmappedField = "UnmappedField1",
+                    MappedConcreteField = "MappedConcreteField1",
+                    UnmappedConcreteField = "UnmappedConcreteField1"
+                },
+            };
+            Array.ForEach(hierarchicalEntries, (item) => this.GetResourceSetEntities<BaseEntry>("HierarchicalEntries").Add(item));
+        }
+
+        protected override void EnsureDataIsInitialized()
+        {
+            if (!dataInitialized)
+            {
+                this.ResetData();
+                dataInitialized = true;
+            }
+        }
+    }
+
+    public class Author
+    {
+        public string Email { get; set; }
+        public string Name { get; set; }
+        public string Uri { get; set; }
+        public Contributor Contributor { get; set; }
+    }
+
+    public class Contributor
+    {
+        public string Email { get; set; }
+        public string Name { get; set; }
+        public string Uri { get; set; }
+    }
+    
+    public class Location
+    {
+        public float Lat { get; set; }
+        public float Long { get; set; }
+    }
+
+    public class Author2
+    {
+        public string Email { get; set; }
+        public string Name { get; set; }
+        public string Uri { get; set; }
+        public Contributor2 Contributor { get; set; }
+    }
+
+    public class Contributor2
+    {
+        public string Email { get; set; }
+        public string Name { get; set; }
+        public string Uri { get; set; }
+    }
+
+    public class Location2
+    {
+        public float Lat { get; set; }
+        public float Long { get; set; }
+    }
+    
+    public class MappedEntry
+    {
+        public int ID { get; set; }
+        public string UnmappedField { get; set; }
+        public Author Author { get; set; }
+        public string Published { get; set; }
+        public string Rights { get; set; }
+        public string Summary { get; set; }
+        public string Title { get; set; }
+        public string Updated { get; set; }
+        public string CustomElement { get; set; }
+        public string CustomAttribute { get; set; }
+        public string NestedElement1 { get; set; }
+        public string NestedElement2 { get; set; }
+        public string CommonAttribute1 { get; set; }
+        public string CommonAttribute2 { get; set; }
+        public Location Location { get; set; }
+    }
+
+    public class ReplicatedEntry
+    {
+        public int ID { get; set; }
+        public string UnmappedField { get; set; }
+        public Author2 Author { get; set; }
+        public string Published { get; set; }
+        public string Rights { get; set; }
+        public string Summary { get; set; }
+        public string Title { get; set; }
+        public string Updated { get; set; }
+        public string CustomElement { get; set; }
+        public string CustomAttribute { get; set; }
+        public string NestedElement1 { get; set; }
+        public string NestedElement2 { get; set; }
+        public string CommonAttribute1 { get; set; }
+        public string CommonAttribute2 { get; set; }
+        public Location2 Location { get; set; }
+    }
+    
+    public class BaseEntry
+    {
+        public int ID { get; set; }
+        public string UnmappedField { get; set; }
+        public string MappedInDerivedField { get; set; }
+        public string MappedField { get; set; }
+    }
+    
+    public class DerivedEntry : BaseEntry
+    {
+        public string UnmappedConcreteField { get; set; }
+        public string MappedConcreteField { get; set; }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/endpoints/ErrorDataService.svc
----------------------------------------------------------------------
diff --git a/JSLib/tests/endpoints/ErrorDataService.svc b/JSLib/tests/endpoints/ErrorDataService.svc
new file mode 100644
index 0000000..d25e30e
--- /dev/null
+++ b/JSLib/tests/endpoints/ErrorDataService.svc
@@ -0,0 +1,56 @@
+<%@ ServiceHost Language="C#" Factory="Microsoft.OData.Service.DataServiceHostFactory, Microsoft.OData.Service, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.ErrorDataService" %>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using Microsoft.OData.Service;
+    using Microsoft.OData.Service.Common;
+    using System.Linq;
+
+    /// <summary>
+    /// A data service that contains in-stream errors
+    /// </summary>
+    public class ErrorDataService : DataService<ErrorDataSource>
+    {
+        // This method is called only once to initialize service-wide policies.
+        public static void InitializeService(DataServiceConfiguration config)
+        {
+            config.SetEntitySetAccessRule("*", EntitySetRights.All);
+            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V4;
+        }
+    }
+
+    public class ErrorDataSource
+    {
+        public IQueryable<ErrorType> Entities
+        {
+            get
+            {
+                ErrorType[] entities = new ErrorType[]
+                {
+                    new ErrorType(() => 0),
+                    new ErrorType(() => { throw new ApplicationException(); })
+                };
+
+                return entities.AsQueryable();
+            }
+        }
+    }
+    
+    public class ErrorType
+    {
+        Func<int> generateID;
+        
+        public ErrorType(Func<int> generateID)
+        {
+            this.generateID = generateID;
+        }
+        
+        public int ID
+        {
+            get { return this.generateID(); }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/endpoints/LargeCollectionService.svc
----------------------------------------------------------------------
diff --git a/JSLib/tests/endpoints/LargeCollectionService.svc b/JSLib/tests/endpoints/LargeCollectionService.svc
new file mode 100644
index 0000000..122d045
--- /dev/null
+++ b/JSLib/tests/endpoints/LargeCollectionService.svc
@@ -0,0 +1,93 @@
+<%@ ServiceHost Language="C#" Factory="Microsoft.OData.Service.DataServiceHostFactory, Microsoft.OData.Service, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.LargeCollectionService" %>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using Microsoft.OData.Service;
+    using Microsoft.OData.Service.Common;
+    using System.Linq;
+    using System.ServiceModel;
+    using System.ServiceModel.Web;
+    using System.Web;
+
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    public class LargeCollectionService : DataService<LargeCollection>
+    {
+        // This method is called only once to initialize service-wide policies.
+        public static void InitializeService(DataServiceConfiguration config)
+        {
+            config.SetEntitySetAccessRule("*", EntitySetRights.All);
+            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
+            config.UseVerboseErrors = true;
+        }
+
+        [WebInvoke]
+        public void ResetData()
+        {
+            this.CurrentDataSource.ResetData();
+        }
+    }
+
+    public class LargeCollection : ReflectionDataContext, IUpdatable
+    {
+        private static bool dataInitialized;
+
+        public IQueryable<Customer> Customers
+        {
+            get { return this.GetResourceSetEntities<Customer>("Customers").AsQueryable(); }
+        }
+
+        public IQueryable<Supplier> Suppliers
+        {
+            get { return this.GetResourceSetEntities<Supplier>("Suppliers").AsQueryable(); }
+        }
+
+        public void ResetData()
+        {
+            this.ClearData();
+
+            IList<Customer> customers = this.GetResourceSetEntities<Customer>("Customers");
+            foreach (int i in Enumerable.Range(1, 2 * 1024 * 1024))
+            {
+                customers.Add(new Customer()
+                {
+                    ID = i,
+                    Name = "Customer " + i
+                });
+            }
+
+            IList<Supplier> suppliers = this.GetResourceSetEntities<Supplier>("Suppliers");
+            foreach (int i in Enumerable.Range(1, 5000))
+            {
+                suppliers.Add(new Supplier()
+                {
+                    ID = i,
+                    Name = "Supplier " + i
+                });
+            }
+        }
+
+        protected override void EnsureDataIsInitialized()
+        {
+            if (!dataInitialized)
+            {
+                this.ResetData();
+                dataInitialized = true;
+            }
+        }
+    }
+
+    public class Customer
+    {
+        public int ID { get; set; }
+        public string Name { get; set; }
+    }
+
+    public class Supplier
+    {
+        public int ID { get; set; }
+        public string Name { get; set; }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-batch-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-batch-functional-tests.html b/JSLib/tests/odata-batch-functional-tests.html
new file mode 100644
index 0000000..c2c3759
--- /dev/null
+++ b/JSLib/tests/odata-batch-functional-tests.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>batch 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/ODataReadOracle.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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="odata-batch-functional-tests.js"></script>
+</head>
+<body>
+    <h1 id="qunit-header">batch tests</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-batch-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-batch-functional-tests.js b/JSLib/tests/odata-batch-functional-tests.js
new file mode 100644
index 0000000..b30e52c
--- /dev/null
+++ b/JSLib/tests/odata-batch-functional-tests.js
@@ -0,0 +1,270 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="../src/odata-batch.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;q=0.1";
+
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    var determineExpected = function (batchRequests) {
+        var expected = 0;
+        $.each(batchRequests, function (_, batchRequest) {
+            // 2 assertions per request: response code and data
+            if (batchRequest.__changeRequests) {
+                expected += batchRequest.__changeRequests.length * 2;
+            } else {
+                expected += 2;
+            }
+        });
+
+        // 2 more assertions than the number of requests in batch: response code and batch response length
+        return expected + 2;
+    };
+
+    var verifyBatchRequest = function(serviceRoot, batchRequests, elementTypes, done) {
+        OData.request({ requestUri: serviceRoot + "/$batch", method: "POST", data: { __batchRequests: batchRequests } },
+            function(data, response) {
+                djstest.assertAreEqual(response.statusCode, httpStatusCode.accepted, "Verify response code");
+                djstest.assertAreEqual(data.__batchResponses.length, batchRequests.length, "Verify batch response count");
+                verifyBatchResponses(batchRequests, elementTypes, serviceRoot, data.__batchResponses, done);
+            },
+            unexpectedErrorHandler, OData.batchHandler);
+    };
+
+    var verifyBatchResponses = function (batchRequests, elementTypes, serviceRoot, batchResponses, done) {
+        forEachAsync(batchRequests, function (index, batchRequest, doneOne) {
+            if (batchRequest.requestUri) {
+                var readFeedOrEntry = elementTypes[index] == "feed" ? ODataReadOracle.readFeed : ODataReadOracle.readEntry;
+                djstest.assertAreEqual(batchResponses[index].statusCode, httpStatusCode.ok, "Verify response code for: GET " + batchRequest.requestUri);
+                readFeedOrEntry(serviceRoot + "/" + batchRequest.requestUri, function (expectedData) {
+                    djstest.assertAreEqualDeep(batchResponses[index].data, expectedData, "Verify data for: GET " + batchRequest.requestUri);
+                    doneOne();
+                }, batchRequests[index].headers.Accept);
+            }
+            else if (batchRequest.__changeRequests) {
+                verifyChangeResponses(batchRequest.__changeRequests, batchResponses[index].__changeResponses, function () { doneOne(); });
+            }
+        }, done);
+    }
+
+    var verifyChangeResponses = function (changeRequests, changeResponses, done) {
+        forEachAsync(changeRequests, function (index, changeRequest, doneOne) {
+            var httpOperation = changeRequest.method + " " + changeRequest.requestUri;
+            var changeResponse = changeResponses[index];
+
+            if (changeRequest.method == "POST") {
+                djstest.assertAreEqual(changeResponse.statusCode, httpStatusCode.created, "Verify response code for: " + httpOperation);
+                ODataReadOracle.readEntry(changeResponse.headers["Location"], function (expectedData) {
+                    djstest.assertAreEqualDeep(changeResponse.data, expectedData, "Verify response data for: " + httpOperation);
+                    doneOne();
+                }, changeRequest.headers.Accept);
+            }
+            else if (changeRequest.method == "PUT") {
+                djstest.assertAreEqual(changeResponse.statusCode, httpStatusCode.noContent, "Verify response code for: " + httpOperation);
+                djstest.assertAreEqual(changeResponse.body, "", "Verify empty body for: " + httpOperation);
+                doneOne();
+            }
+            else if (changeRequest.method == "DELETE") {
+                djstest.assertAreEqual(changeResponse.statusCode, httpStatusCode.noContent, "Verify response code for: " + httpOperation);
+                djstest.assertAreEqual(changeResponse.body, "", "Verify empty body for: " + httpOperation);
+                doneOne();
+            }
+        }, done);
+    }
+
+    var forEachAsync = function (array, callback, done) {
+        var count = 0;
+        var doneOne = function () {
+            count++;
+            if (count == array.length) {
+                done();
+            }
+        }
+
+        $.each(array, function (index, element) { callback(index, element, doneOne); });
+    };
+
+    var service = "./endpoints/FoodStoreDataServiceV4.svc";
+    var batchUri = service + "/$batch";
+
+    var httpStatusCode = {
+        ok: 200,
+        created: 201,
+        accepted: 202,
+        noContent: 204
+    };
+
+    var mimeTypes = [undefined, /*"application/atom+xml",*/ "application/json;odata.metadata=minimal"];
+
+    module("Functional", {
+        setup: function () {
+            djstest.wait(function (done) {
+                $.post(service + "/ResetData", done);
+            });
+        }
+    });
+
+    $.each(mimeTypes, function (_, mimeType) {
+        var acceptHeaders = mimeType ? { Accept: mimeType} : undefined;
+        var mimeHeaders = mimeType ? { "Content-Type": mimeType, Accept: mimeType} : undefined;
+
+        djstest.addTest(function multipleRetrieves(acceptHeaders) {
+            var uriSegments = ["Foods(0)", "Foods?$filter=FoodID eq 1", "Foods?$top=2"];
+            var elementTypes = ["entry", "feed", "feed"];
+
+            var batchRequests = $.map(uriSegments, function (uriSegment) {
+                return { requestUri: uriSegment, method: "GET", headers: acceptHeaders }
+            });
+
+            djstest.assertsExpected(determineExpected(batchRequests));
+            verifyBatchRequest(service, batchRequests, elementTypes, function () { djstest.done(); });
+        }, "Multiple retrieves: mimeType = " + mimeType, acceptHeaders);
+
+        djstest.addTest(function multipleChangesets(params) {
+            var batchRequests = [
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories", method: "POST", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            }
+                        ]
+                    },
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories(1)", method: "PUT", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 1, Name: "Updated Category" }
+                            },
+                            { requestUri: "Categories(0)", method: "DELETE", headers: djstest.clone(params.acceptHeaders) }
+                        ]
+                    }
+                ];
+            var elementTypes = [null, null];
+
+            djstest.assertsExpected(determineExpected(batchRequests));
+            verifyBatchRequest(service, batchRequests, elementTypes, function () { djstest.done(); });
+        }, "Multiple changesets: mimeType = " + mimeType, { acceptHeaders: acceptHeaders, mimeHeaders: mimeHeaders });
+
+        djstest.addTest(function multipleRetrievesAndChangesets(params) {
+            // Header needs to be cloned because it is mutable; this means that after processing one request in the batch
+            // the header object will be modified
+            var batchRequests = [
+                    { requestUri: "Foods(0)", method: "GET", headers: djstest.clone(params.acceptHeaders) },
+                    { requestUri: "Foods?$filter=FoodID eq 1", method: "GET", headers: djstest.clone(params.acceptHeaders) },
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories", method: "POST", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            }
+                        ]
+                    },
+                    { requestUri: "Foods?$top=2", method: "GET", headers: djstest.clone(params.acceptHeaders) },
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories(1)", method: "PUT", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 1, Name: "Updated Category" }
+                            },
+                            { requestUri: "Categories(0)", method: "DELETE", headers: djstest.clone(params.acceptHeaders) }
+                        ]
+                    }
+                ];
+            var elementTypes = ["entry", "feed", null, "feed", null];
+
+            djstest.assertsExpected(determineExpected(batchRequests));
+            verifyBatchRequest(service, batchRequests, elementTypes, function () { djstest.done(); });
+        }, "Multiple retrieves and changesets: mimeType = " + mimeType, { acceptHeaders: acceptHeaders, mimeHeaders: mimeHeaders });
+
+        djstest.addTest(function singleRetrieve(acceptHeaders) {
+            var batchRequests = [{ requestUri: "Foods(2)", method: "GET", headers: acceptHeaders}];
+            var elementTypes = ["entry"];
+
+            djstest.assertsExpected(determineExpected(batchRequests));
+            verifyBatchRequest(service, batchRequests, elementTypes, function () { djstest.done(); });
+        }, "Single retrieve: mimeType = " + mimeType, acceptHeaders);
+
+        djstest.addTest(function singleChangeset(params) {
+            var batchRequests = [
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories", method: "POST", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            },
+                            { requestUri: "Categories(1)", method: "PUT", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 1, Name: "Updated Category" }
+                            }
+                        ]
+                    }
+                ];
+            var elementTypes = [null];
+
+            djstest.assertsExpected(determineExpected(batchRequests));
+            verifyBatchRequest(service, batchRequests, elementTypes, function () { djstest.done(); });
+        }, "Single changeset: mimeType = " + mimeType, { acceptHeaders: acceptHeaders, mimeHeaders: mimeHeaders });
+
+        djstest.addTest(function singleRetrieveAndChangeset(params) {
+            var batchRequests = [
+                    { requestUri: "Foods(0)", method: "GET", headers: djstest.clone(params.acceptHeaders) },
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories", method: "POST", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            },
+                            { requestUri: "Categories(1)", method: "PUT", headers: djstest.clone(params.mimeHeaders), data:
+                                { CategoryID: 1, Name: "Updated Category" }
+                            }
+                        ]
+                    }
+                ];
+            var elementTypes = ["entry", null];
+
+            djstest.assertsExpected(determineExpected(batchRequests));
+            verifyBatchRequest(service, batchRequests, elementTypes, function () { djstest.done(); });
+        }, "Single retrieve and changeset: mimeType = " + mimeType, { acceptHeaders: acceptHeaders, mimeHeaders: mimeHeaders });
+    });
+
+    djstest.addTest(function updateOutsideChangeset() {
+        var batchRequests = [{ requestUri: "Categories", method: "POST", data: { CategoryID: 42, Name: "New Category"}}];
+
+        djstest.assertsExpected(1);
+        OData.request({ requestUri: batchUri, method: "POST", data: { __batchRequests: batchRequests} },
+            function (data, response) {
+                djstest.assert(response.body.indexOf("An error occurred while processing this request.") > -1, "Verify response error message");
+                djstest.done();
+            }, unexpectedErrorHandler, OData.batchHandler
+        );
+    }, "Update outside changeset");
+
+    djstest.addTest(function retrieveInsideChangeset() {
+
+        var batchRequests = [
+                    { requestUri: "Foods(0)", method: "GET" },
+                    { __changeRequests: [
+                            { requestUri: "Categories", method: "POST", data: { CategoryID: 42, Name: "New Category"} },
+                            { requestUri: "Categories(1)", method: "PUT", data: { CategoryID: 1, Name: "Updated Category"} }
+                        ]
+                    },
+                    { requestUri: "Foods(1)", method: "GET" },
+                    { __changeRequests: [
+                            { requestUri: "Categories", method: "POST", data: { CategoryID: 42, Name: "New Category"} },
+                            { requestUri: "Categories(1)", method: "PUT", data: { CategoryID: 1, Name: "Updated Category"} },
+                            { requestUri: "Foods", method: "GET" }
+                        ]
+                    }
+                ];
+
+        OData.request({ requestUri: batchUri, method: "POST", data: { __batchRequests: batchRequests} },
+            function (data, response) {
+                var batchResponses = data.__batchResponses;
+                var error = batchResponses[3].__changeResponses[0];
+                djstest.assert(error.response.body.indexOf("An error occurred while processing this request.") > -1, "Response contains expected message");
+                // Verify that the responses prior to the error are the expected ones.
+                batchRequests.splice(3, 1);
+                batchResponses.splice(3, 1);
+                verifyBatchResponses(batchRequests, ["entry", null], service, batchResponses, function () { djstest.done(); });
+            }, unexpectedErrorHandler, OData.batchHandler);
+    }, "Retrieve inside changeset");
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-batch-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-batch-tests.js b/JSLib/tests/odata-batch-tests.js
new file mode 100644
index 0000000..9aeb033
--- /dev/null
+++ b/JSLib/tests/odata-batch-tests.js
@@ -0,0 +1,551 @@
+/// <reference path="../src/odata-net.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/djstest.js" />
+/// <reference path="common/mockHttpClient.js" />
+
+// odata-batch-tests.js
+
+(function (window, undefined) {
+    // DATAJS INTERNAL START
+    var defaultAcceptString = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;q=0.1";
+
+    var testPayload = {
+        CategoryID : 42,
+        Name: "New Category",
+        ID : "odata",
+        version: "4.0"
+    };
+    
+    // to do: test Atom payload
+    // var atomPayload = OData.atomSerializer(OData.atomHandler, testPayload, { "OData-Version": "4.0" });
+    var jsonPayload = OData.jsonSerializer(OData.jsonHandler, testPayload, { "OData-Version": "4.0" });
+
+    djstest.addTest(function writeRequestTest() {
+        var request = {
+            headers: { "Content-Type": "plain/text; charset=utf-8", Accept: "*/*", "OData-Version": "2.0" },
+            requestUri: "http://temp.org",
+            method: "GET",
+            body: "test request"
+        };
+        var expected = "GET http://temp.org HTTP/1.1\r\n" +
+                       "Content-Type: plain/text; charset=utf-8\r\n" +
+                       "Accept: */*\r\n" +
+                       "OData-Version: 2.0\r\n" +
+                       "\r\n" +
+                       "test request";
+
+        var actual = OData.writeRequest(request);
+        djstest.assertAreEqual(actual, expected, "WriteRequest serializes a request properly");
+        djstest.done();
+    });
+
+    djstest.addTest(function serializeSimpleBatchTest() {
+
+        var request = {
+            requestUri: "http://temp.org",
+            method: "POST",
+            data: { __batchRequests: [
+                { requestUri: "http://feed(1)", headers: {} },
+                { requestUri: "http://feed(2)", headers: { "Accept": "application/json;odata.metadata=minimal" }, method: "GET" }
+            ]
+            }
+        };
+
+        var template = "\r\n--<batchBoundary>\r\n" +
+                       "Content-Type: application/http\r\n" +
+                       "Content-Transfer-Encoding: binary\r\n" +
+                       "\r\n" +
+                       "GET http://feed(1) HTTP/1.1\r\n" +
+                       "Accept: " + defaultAcceptString + "\r\n" +
+                       "OData-MaxVersion: 4.0\r\n" +
+                       "\r\n" +
+                       "\r\n--<batchBoundary>\r\n" +
+                       "Content-Type: application/http\r\n" +
+                       "Content-Transfer-Encoding: binary\r\n" +
+                       "\r\n" +
+                       "GET http://feed(2) HTTP/1.1\r\n" +
+                       "Accept: application/json;odata.metadata=minimal\r\n" +
+                       "OData-MaxVersion: 4.0\r\n" +
+                       "\r\n" +
+                       "\r\n--<batchBoundary>--\r\n";
+
+        MockHttpClient.clear().addRequestVerifier(request.requestUri, function (request) {
+            var cType = OData.contentType(request.headers["Content-Type"]);
+            var boundary = cType.properties["boundary"];
+            var expected = template.replace(/<batchBoundary>/g, boundary);
+
+            djstest.assert(boundary, "Request content type has its boundary set");
+            djstest.assertAreEqual(request.body, expected, "Request body is serialized properly");
+            djstest.done();
+        });
+
+        OData.request(request, null, null, OData.batchHandler, MockHttpClient);
+    });
+
+    djstest.addTest(function serializeComplexBatchTest() {
+
+        var request = {
+            requestUri: "http://temp.org",
+            method: "POST",
+            data: { __batchRequests: [
+                { requestUri: "http://feed(1)", headers: {} },
+                { requestUri: "http://feed(2)", headers: { "Accept": "application/json;odata.metadata=minimal" }, method: "GET" },
+                { __changeRequests: [
+                        { requestUri: "http://feed(1)", headers: {}, method: "POST", data: testPayload }
+                        // to do: test atom payload                       
+//                        { requestUri: "http://feed(2)", headers: { "Content-Type": "application/atom+xml", "OData-Version": "4.0" }, method: "PUT", data: testPayload }//
+                        ]
+                },
+                { requestUri: "http://feed(1)", headers: {} }
+            ]
+            }
+        };
+
+        // 
+        var template = "\r\n--<batchBoundary>\r\n" +
+                       "Content-Type: application/http\r\n" +
+                       "Content-Transfer-Encoding: binary\r\n" +
+                       "\r\n" +
+                       "GET http://feed(1) HTTP/1.1\r\n" +
+                       "Accept: " + defaultAcceptString + "\r\n" +
+                       "OData-MaxVersion: 4.0\r\n" +
+                       "\r\n" +
+                       "\r\n--<batchBoundary>\r\n" +
+                       "Content-Type: application/http\r\n" +
+                       "Content-Transfer-Encoding: binary\r\n" +
+                       "\r\n" +
+                       "GET http://feed(2) HTTP/1.1\r\n" +
+                       "Accept: application/json;odata.metadata=minimal\r\n" +
+                       "OData-MaxVersion: 4.0\r\n" +
+                       "\r\n" +
+                       "\r\n--<batchBoundary>\r\n" +
+                       "Content-Type: multipart/mixed; boundary=<changesetBoundary>\r\n" +
+                       "\r\n--<changesetBoundary>\r\n" +
+                       "Content-Type: application/http\r\n" +
+                       "Content-Transfer-Encoding: binary\r\n" +
+                       "\r\n" +
+                       "POST http://feed(1) HTTP/1.1\r\n" +
+                       "Accept: " + defaultAcceptString + "\r\n" +
+                       "OData-Version: 4.0\r\n" +
+                       "Content-Type: application/json\r\n" +
+                       "OData-MaxVersion: 4.0\r\n" +
+                       "\r\n" +
+                       jsonPayload +
+                       "\r\n--<changesetBoundary>--\r\n" +
+                       "\r\n--<batchBoundary>\r\n" +
+                       "Content-Type: application/http\r\n" +
+                       "Content-Transfer-Encoding: binary\r\n" +
+                       "\r\n" +
+                       "GET http://feed(1) HTTP/1.1\r\n" +
+                       "Accept: " + defaultAcceptString + "\r\n" +
+                       "OData-MaxVersion: 4.0\r\n" +
+                       "\r\n" +
+                       "\r\n--<batchBoundary>--\r\n";
+
+        MockHttpClient.clear().addRequestVerifier(request.requestUri, function (request) {
+            // Get the boundaries from the request.
+            var start = request.body.indexOf("multipart/mixed");
+            var end = request.body.indexOf("\r\n", start);
+
+            var csetBoundary = OData.contentType(request.body.substring(start, end)).properties["boundary"];
+            var batchBoundary = OData.contentType(request.headers["Content-Type"]).properties["boundary"];
+
+            var expected = template.replace(/<batchBoundary>/g, batchBoundary);
+            expected = expected.replace(/<changesetBoundary>/g, csetBoundary);
+
+            djstest.assert(batchBoundary, "Request content type has its boundary set");
+            djstest.assert(csetBoundary, "Changeset content type has its boundary set");
+            djstest.assertAreEqual(request.body, expected, "Request body is serialized properly");
+            djstest.done();
+        });
+
+        OData.request(request, null, null, OData.batchHandler, MockHttpClient);
+    });
+
+    djstest.addTest(function serializeChangeSetTest() {
+        var request = {
+            requestUri: "http://temp.org",
+            method: "POST",
+            data: {
+                __batchRequests: [
+                    { __changeRequests: [
+                        { requestUri: "http://feed(1)", headers: {}, method: "POST", data: testPayload }
+//                        { requestUri: "http://feed(2)", headers: { "Content-Type": "application/atom+xml", "OData-Version": "4.0" }, method: "PUT", data: testPayload }
+                        ]
+                    }
+            ]
+            }
+        };
+
+        // To do: test atom payload
+        var template = "\r\n--<batchBoundary>\r\n" +
+                       "Content-Type: multipart/mixed; boundary=<changesetBoundary>\r\n" +
+                       "\r\n--<changesetBoundary>\r\n" +
+                       "Content-Type: application/http\r\n" +
+                       "Content-Transfer-Encoding: binary\r\n" +
+                       "\r\n" +
+                       "POST http://feed(1) HTTP/1.1\r\n" +
+                       "Accept: " + defaultAcceptString + "\r\n" +
+                       "OData-Version: 4.0\r\n" +
+                       "Content-Type: application/json\r\n" +
+                       "OData-MaxVersion: 4.0\r\n" +
+                       "\r\n" +
+                       jsonPayload +
+                       "\r\n--<changesetBoundary>--\r\n" +
+                       "\r\n--<batchBoundary>--\r\n";
+
+        MockHttpClient.clear().addRequestVerifier(request.requestUri, function (request) {
+            // Get the boundaries from the request.
+            var start = request.body.indexOf("multipart/mixed");
+            var end = request.body.indexOf("\r\n", start);
+
+            var csetBoundary = OData.contentType(request.body.substring(start, end)).properties["boundary"];
+            var batchBoundary = OData.contentType(request.headers["Content-Type"]).properties["boundary"];
+
+            var expected = template.replace(/<batchBoundary>/g, batchBoundary);
+            expected = expected.replace(/<changesetBoundary>/g, csetBoundary);
+
+            djstest.assert(batchBoundary, "Request content type has its boundary set");
+            djstest.assert(csetBoundary, "Changeset content type has its boundary set");
+            djstest.assertAreEqual(request.body, expected, "Request body is serialized properly");
+            djstest.done();
+        });
+
+        OData.request(request, null, null, OData.batchHandler, MockHttpClient);
+    });
+
+    djstest.addTest(function serializeNestedChangeSetsTest() {
+        var request = {
+            requestUri: "http://temp.org",
+            method: "POST",
+            data: testPayload
+        };
+
+        djstest.expectException(function () {
+            OData.request(request, null, null, OData.batchHandler);
+        });
+
+        djstest.done();
+    });
+
+    djstest.addTest(function serializeNonBatchObjectTest() {
+        var request = {
+            requestUri: "http://temp.org",
+            method: "POST",
+            data: {
+                __batchRequests: [
+                    { __changeRequests: [
+                        { __changeRequests: [
+                            { requestUri: "http://feed(2)", headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, method: "PUT", data: testPayload }
+                        ]
+                        }
+                    ]
+                    }
+            ]
+            }
+        };
+
+        djstest.expectException(function () {
+            OData.request(request, null, null, OData.batchHandler);
+        });
+
+        djstest.done();
+    });
+
+    djstest.addTest(function readSimpleBatchTest() {
+        var response = {
+            statusCode: 202,
+            statusText: "Accepted",
+            headers: {
+                "Content-Type": "multipart/mixed; boundary=batchresponse_b61ab173-39c7-45ea-ade4-941efae85ab9"
+            },
+            body: "--batchresponse_b61ab173-39c7-45ea-ade4-941efae85ab9\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 201 Created\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\r\n\
+X-Content-Type-Options: nosniff\r\n\
+Cache-Control: no-cache\r\n\
+Location: http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(42)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Categories/$entity\",\"Icon@odata.mediaContentType\":\"image/gif\",\"CategoryID\":42,\"Name\":\"New Category\"}\r\n\
+--batchresponse_b61ab173-39c7-45ea-ade4-941efae85ab9\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 201 Created\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\r\n\
+X-Content-Type-Options: nosniff\r\n\
+Cache-Control: no-cache\r\n\
+Location: http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(43)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Categories/$entity\",\"Icon@odata.mediaContentType\":\"image/gif\",\"CategoryID\":43,\"Name\":\"New Category\"}\r\n\
+--batchresponse_b61ab173-39c7-45ea-ade4-941efae85ab9--\r\n\
+"
+        };
+
+        MockHttpClient.clear().addResponse("http://testuri.org", response);
+        OData.read("http://testuri.org", function (data, response) {
+            djstest.assert(data.__batchResponses, "data.__batchResponses is defined");
+            djstest.assertAreEqual(data.__batchResponses[0].headers["Location"], "http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(42)", "part 1 of the response was read");
+            djstest.assertAreEqual(data.__batchResponses[1].headers["Location"], "http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(43)", "part 2 of the response was read");
+            djstest.assertAreEqual(data.__batchResponses[0].data["CategoryID"], 42, "part 1 data of the response was read");
+            djstest.assertAreEqual(data.__batchResponses[1].data["CategoryID"], 43, "part 2 data of the response was read");
+            djstest.done();
+        }, null, OData.batchHandler, MockHttpClient);
+    });
+
+    djstest.addTest(function readBatchWithChangesetTest() {
+        var response = {
+            statusCode: 202,
+            statusText: "Accepted",
+            headers: {
+                "Content-Type": "multipart/mixed; boundary=batchresponse_fb681875-73dc-4e62-9898-a0af89021341"
+            },
+            body: "--batchresponse_fb681875-73dc-4e62-9898-a0af89021341\r\n\
+Content-Type: multipart/mixed; boundary=changesetresponse_905a1494-fd76-4846-93f9-a3431f0bf5a2\r\n\
+\r\n\
+--changesetresponse_905a1494-fd76-4846-93f9-a3431f0bf5a2\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 201 OK\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\r\n\
+X-Content-Type-Options: nosniff\r\n\
+Cache-Control: no-cache\r\n\
+Location: http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(42)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Categories/$entity\",\"Icon@odata.mediaContentType\":\"image/gif\",\"CategoryID\":42,\"Name\":\"New Category\"}\r\n\
+--changesetresponse_905a1494-fd76-4846-93f9-a3431f0bf5a2\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 204 No Content\r\n\
+X-Content-Type-Options: nosniff\r\n\
+Cache-Control: no-cache\r\n\
+OData-Version: 4.0;\r\n\
+\r\n\
+\r\n\
+--changesetresponse_905a1494-fd76-4846-93f9-a3431f0bf5a2--\r\n\
+--batchresponse_fb681875-73dc-4e62-9898-a0af89021341\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 201 Created\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\r\n\
+X-Content-Type-Options: nosniff\r\n\
+Cache-Control: no-cache\r\n\
+Location: http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(41)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Categories/$entity\",\"Icon@odata.mediaContentType\":\"image/gif\",\"CategoryID\":41,\"Name\":\"New Category\"}\r\n\
+--batchresponse_fb681875-73dc-4e62-9898-a0af89021341\r\n\
+Content-Type: multipart/mixed; boundary=changesetresponse_92cc2ae8-a5f2-47fc-aaa3-1ff9e7453b07\r\n\
+\r\n\
+--changesetresponse_92cc2ae8-a5f2-47fc-aaa3-1ff9e7453b07\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 201 OK\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\r\n\
+X-Content-Type-Options: nosniff\r\n\
+Cache-Control: no-cache\r\n\
+Location: http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(43)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Categories/$entity\",\"Icon@odata.mediaContentType\":\"image/gif\",\"CategoryID\":43,\"Name\":\"New Category\"}\r\n\
+--changesetresponse_92cc2ae8-a5f2-47fc-aaa3-1ff9e7453b07\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 204 No Content\r\n\
+X-Content-Type-Options: nosniff\r\n\
+Cache-Control: no-cache\r\n\
+OData-Version: 4.0;\r\n\
+\r\n\
+\r\n\
+--changesetresponse_92cc2ae8-a5f2-47fc-aaa3-1ff9e7453b07--\r\n\
+--batchresponse_fb681875-73dc-4e62-9898-a0af89021341--\r\n\
+"
+        };
+
+        MockHttpClient.clear().addResponse("http://testuri.org", response);
+        OData.read("http://testuri.org", function (data, response) {
+
+            var batchResponses = data.__batchResponses;
+            djstest.assert(batchResponses, "data contains the batch responses");
+
+            var changesetResponses = batchResponses[0].__changeResponses;
+            djstest.assert(changesetResponses, "batch response 1 contains the change set responses");
+            var changesetResponses3 = batchResponses[2].__changeResponses;
+            djstest.assert(changesetResponses3, "batch response 3 contains the change set responses");
+            
+            djstest.assertAreEqual(batchResponses[0].data, undefined, "No data defined for batch response 1");
+            djstest.assertAreEqual(changesetResponses[0].headers["Location"], "http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(42)", "part 1 of the changeset response of the response 1 was read");
+            djstest.assertAreEqual(changesetResponses[0].data["CategoryID"], 42, "part 1 data of the changeset response of the response 1 was read");
+            djstest.assertAreEqual(changesetResponses[1].data, undefined, "No data defined for no content only response in part 2 of the changeset response of the response 1");
+            
+            djstest.assertAreEqual(batchResponses[1].headers["Location"], "http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(41)", "response 2 was read");
+            djstest.assertAreEqual(batchResponses[1].data["CategoryID"], 41, "response 2 data was read");
+            
+            djstest.assertAreEqual(batchResponses[2].data, undefined, "No data defined for");
+            djstest.assertAreEqual(changesetResponses3[0].headers["Location"], "http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(43)", "part 1 of the changeset response of the response 3 was read");
+            djstest.assertAreEqual(changesetResponses3[0].data["CategoryID"], 43, "part 1 data of the changeset response of the response 3 was read");
+            djstest.assertAreEqual(changesetResponses3[1].data, undefined, "No data defined for no content only response in part 2 of the changeset response of the response 3");
+            djstest.done();
+        }, null, OData.batchHandler, MockHttpClient);
+    });
+
+    djstest.addTest(function readBatchWithErrorPartTest() {
+        var response = {
+            statusCode: 202,
+            statusText: "Accepted",
+            headers: {
+                "Content-Type": "multipart/mixed; boundary=batchresponse_9402a3ab-260f-4fa4-af01-0b30db397c8d"
+            },
+            body: "--batchresponse_9402a3ab-260f-4fa4-af01-0b30db397c8d\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 200 OK\r\n\
+Cache-Control: no-cache\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;charset=utf-8\r\n\
+Location: http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(1)\r\n\
+\r\n\
+{\"error\":{\"code\":\"\",\"message\":\"Resource not found for the segment 'Categories(1)'.\"}}\r\n\
+--batchresponse_9402a3ab-260f-4fa4-af01-0b30db397c8d\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 400 Bad Request\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json\r\n\
+{\"error\":{\"code\":\"\",\"message\":\"Error processing request stream.'.\"}}\r\n\
+--batchresponse_9402a3ab-260f-4fa4-af01-0b30db397c8d--\r\n\
+"
+        };
+
+        MockHttpClient.clear().addResponse("http://testuri.org", response);
+        OData.read("http://testuri.org", function (data, response) {
+            var batchResponses = data.__batchResponses;
+            djstest.assert(batchResponses, "data.__batchResponses is defined");
+            djstest.assertAreEqual(batchResponses.length, 2, "batch contains two responses");
+            djstest.assertAreEqual(batchResponses[0].headers["Location"], "http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(1)", "part 1 of the response was read");
+            djstest.assert(batchResponses[1].response, "part 2 of the response was read");
+            djstest.done();
+        }, null, OData.batchHandler, MockHttpClient);
+    });
+
+
+    djstest.addTest(function readMalformedMultipartResponseThrowsException() {
+        var response = {
+            statusCode: 202,
+            statusText: "Accepted",
+            headers: {
+                "Content-Type": "multipart/mixed; boundary=batchresponse_fb681875-73dc-4e62-9898-a0af89021341"
+            },
+            body: "--batchresponse_fb681875-73dc-4e62-9898-a0af89021341\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 200 OK\r\n\
+Cache-Control: no-cache\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;charset=utf-8\r\n\
+Location: http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(1)\r\n\
+\r\n\
+{\"error\":{\"code\":\"\",\"message\":\"Resource not found for the segment 'Categories(1)'.\"}}\r\n\
+--batchresponse_fb681875-73dc-4e62-9898-a0af89021341\r\n\
+Content-Type: multipart/mixed; boundary=changesetresponse_2f9c6ba7-b330-4e7c-bf2a-db521996c243\r\n\
+\r\n\
+--changesetresponse_2f9c6ba7-b330-4e7c-bf2a-db521996c243\r\n\
+Content-Type: application/http\r\n\
+Content-Transfer-Encoding: binary\r\n\
+\r\n\
+HTTP/1.1 404 Not Found\r\n\
+X-Content-Type-Options: nosniff\r\n\
+OData-Version: 4.0;\r\n\
+Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\r\n\
+\r\n\
+{\"error\":{\"code\":\"\",\"message\":\GET operation cannot be specified in a change set. Only PUT, POST and DELETE operations can be specified in a change set..'.\"}}\r\n\
+--changesetresponse_2f9c6ba7-b330-4e7c-bf2a-db521996c243--\r\n\
+--batchresponse_fb681875-73dc-4e62-9898-a0af89021341--\r\n\
+"
+        };
+
+        MockHttpClient.clear().addResponse("http://testuri.org", response);
+        OData.read("http://testuri.org", function (data, response) {
+            var batchResponses = data.__batchResponses;
+            djstest.assert(batchResponses, "data.__batchResponses is defined");
+            djstest.assertAreEqual(batchResponses.length, 2, "batch contains two responses");
+            djstest.assertAreEqual(batchResponses[0].headers["Location"], "http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(1)", "part 1 of the response was read");
+
+            var error = batchResponses[1].__changeResponses[0];
+            djstest.assert(error.response.body.indexOf("GET operation cannot be specified in a change set") > -1, "Response contains expected message");
+            djstest.done();
+        }, null, OData.batchHandler, MockHttpClient);
+        djstest.done();
+    });
+
+    djstest.addTest(function batchRequestContextIsPushedToThePartsHandlersTest() {
+        var testHandler = {
+            read: function (response, context) {
+                djstest.assert(context.recognizeDates, "Recognize dates was set properly on the part request context");
+            },
+            write: function (request, context) {
+                djstest.assert(context.recognizeDates, "Recognize dates was set properly on the part request context");
+            }
+        };
+
+        var batch = {
+            headers: {},
+            __batchRequests: [
+                { requestUri: "http://someUri" },
+                { __changeRequests: [
+                     { requestUri: "http://someUri", method: "POST", data: { p1: 500} }
+                  ]
+                }
+            ]
+        };
+
+        var request = { requestUri: "http://someuri", headers: {}, data: batch };
+        var response = {
+            statusCode: 202,
+            statusText: "Accepted",
+            headers: {
+                "Content-Type": "multipart/mixed; boundary=batchresponse_fb681875-73dc-4e62-9898-a0af89021341"
+            },
+            body: '--batchresponse_fb681875-73dc-4e62-9898-a0af89021341\r\n' +
+                  'Content-Type: application/http\r\n' +
+                  'Content-Transfer-Encoding: binary\r\n' +
+                  '\r\n' +
+                  'HTTP/1.1 200 OK\r\n' +
+                  'Cache-Control: no-cache\r\n' +
+                  'OData-Version: 1.0;\r\n' +
+                  'Content-Type: application/json\r\n' +
+                  '\r\n' +
+                  '{ "p1": 500 }\r\n' +
+                  '\r\n' +
+                  '--batchresponse_fb681875-73dc-4e62-9898-a0af89021341--\r\n'
+        };
+
+        var oldPartHandler = OData.batchHandler.partHandler;
+
+        OData.batchHandler.partHandler = testHandler;
+
+        OData.batchHandler.write(request, { recognizeDates: true });
+        OData.batchHandler.read(response, { recognizeDates: true });
+
+        OData.batchHandler.partHandler = oldPartHandler;
+
+        djstest.done();
+    });
+
+
+    // DATAJS INTERNAL END
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-cache-filter-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-filter-functional-tests.html b/JSLib/tests/odata-cache-filter-functional-tests.html
new file mode 100644
index 0000000..9c5da7e
--- /dev/null
+++ b/JSLib/tests/odata-cache-filter-functional-tests.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>datajs.cache  filter functional tests</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+    <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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+    
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="common/CacheOracle.js"></script>
+    <script type="text/javascript" src="common/ObservableHttpClient.js"></script>
+    <script type="text/javascript" src="common/ODataReadOracle.js"></script>
+    <script type="text/javascript" src="odata-cache-filter-functional-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">datajs.cache filter functional tests</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-cache-filter-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-filter-functional-tests.js b/JSLib/tests/odata-cache-filter-functional-tests.js
new file mode 100644
index 0000000..b11cad3
--- /dev/null
+++ b/JSLib/tests/odata-cache-filter-functional-tests.js
@@ -0,0 +1,416 @@
+/// <reference path="../src/datajs.js" />
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/cache.js" />
+/// <reference path="common/djstest.js" />
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;q=0.1";
+    var feeds = [
+        { feed: "./endpoints/FoodStoreDataServiceV4.svc/Foods" }
+    ];
+
+    var itemsInCollection = 16;
+
+    var pageSize = 3;
+    var readRangeStart = pageSize + 1;
+    var readRangeTake = pageSize;
+
+    // Indices for filterForward after an initial readRange has partially filled the cache
+    var filterForwardAfterReadIndices = [
+        readRangeStart - 1, // before items in the cache
+        readRangeStart, // beginning of items in the cache
+        readRangeStart + readRangeTake, // last item prefetched in the cache
+        readRangeStart + readRangeTake + 1 // past range already in cache
+    ];
+
+    // Indices for filterBack after an initial readRange has partially filled the cache
+    var filterBackAfterReadIndices = [
+        readRangeStart - 1, // before items in the cache
+        readRangeStart, // beginning of items in the cache
+        readRangeStart + readRangeTake, // last item prefetched in the cache
+        readRangeStart + readRangeTake + 1 // past range already in cache
+    ];
+
+    // Counts for filterForward after a readRange has partially filled the cache
+    var filterForwardAfterReadCounts = [
+        -5, // Get all items
+        3, // Subset of all items found in the cache
+        itemsInCollection
+    ];
+
+    // Counts for filterBack after a readRange has partially filled the cache
+    var filterBackAfterReadCounts = [
+        -5, // Get all items
+        3, // Subset of all items found in the cache
+        itemsInCollection
+    ];
+
+    // Indices for single filterForward
+    var singleFilterForwardIndices = [
+        -5,
+        itemsInCollection - 1,
+        itemsInCollection, // beyond the end of the collection
+        itemsInCollection + pageSize // more than one page beyond the collection
+    ];
+
+    // Indices for single filterBack
+    var singleFilterBackIndices = [
+        -1,
+        0,
+        itemsInCollection - 1
+    ];
+
+    // Count for single filterForward
+    var singleFilterForwardCounts = [
+        5,
+        itemsInCollection + 1 // more than number of items in collection
+    ];
+
+    // Count for single filterBack
+    var singleFilterBackCounts = [
+        5,
+        itemsInCollection + 1 // more than number of items in collection
+    ];
+
+    // Index/count variations for multiple filterForwards
+    var multipleFilterForwards = [
+        { index: 0, count: -1 },  // everything
+        {index: 2, count: 5 },   // range in first half
+        {index: 4, count: 7 },   // range in the middle to overlap first and second half
+        {index: 9, count: 4}    // range in second half
+    ];
+
+    // Index/count variations for multiple filterBacks
+    var multipleFilterBacks = [
+        { index: itemsInCollection - 1, count: -1 },  // everything
+        {index: itemsInCollection - 2, count: 5 },   // range in second half
+        {index: itemsInCollection - 4, count: 7 },   // range in the middle to overlap first and second half
+        {index: itemsInCollection - 9, count: 4}    // range in first half
+    ];
+
+
+    var invalidIndices = [NaN, undefined, Infinity, "not a valid value"];
+    var invalidCounts = [NaN, undefined, Infinity, "not a valid value"];
+
+    // Predicate which returns all items in the collection
+    var getAllItemsPredicate = function (item) {
+        return true;
+    };
+
+    // Predicate which returns none of the items in the collection
+    var getNoItemsPredicate = function (item) {
+        return false;
+    };
+
+    var getEveryThirdPredicate = function (item) {
+        return ((item.FoodID % 3) === 0);
+    };
+
+    var filterPredicates = [
+        getAllItemsPredicate,
+        getNoItemsPredicate,
+        getEveryThirdPredicate
+    ];
+
+    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 validateFilterResultsAndRequests = function (feed, cache, index, count, predicate, finished, backwards, session, cacheOracle) {
+        /// <summary>Runs filter and validates the results and network requests</summary>
+        /// <param name="feed" type="Object">The feed being read from</param>
+        /// <param name="cache" type="Object">The cache to perform the filter on</param>
+        /// <param name="index" type="Integer">The index value</param>
+        /// <param name="count" type="Integer">The count value</param>
+        /// <param name="predicate" type="Object">Filter string to append to the feed to validate the predicate</param>
+        /// <param name="finished" type="Function">Callback function called after data is verified</param>
+        /// <param name="session" type="Object">Session object to validate the network requests</param>
+        /// <param name="cacheOracle" type="Object">cacheOracle object to validate the network requests</param>
+
+        if (count < 0) {
+            count = itemsInCollection;
+        }
+
+        if (index < 0) {
+            index = 0;
+        }
+
+        window.ODataReadOracle.readJsonAcrossServerPages(feed, function (expectData) {
+            if (backwards) {
+                cache.filterBack(index, count, predicate).then(function (actualResults) {
+                    var expectedResults = CacheOracle.getExpectedFilterResults(expectData, index, count, predicate, backwards);
+                    djstest.assertAreEqualDeep(actualResults, expectedResults, "results for " + "filterBack requests");
+
+                    if (session && cacheOracle) {
+                        // If the count is not satisfied in the expected results, read to the beginning of the collection
+                        // otherwise read to the first expected index
+                        var firstIndex = 0; 
+                        if (expectedResults.value.length != 0) {
+                            firstIndex = (expectedResults.value.length < count) ? 0 : expectedResults.value[0].index;
+                        }
+                        // The effective count is the number of items between the first and last index
+                        var expectedCount = index - firstIndex + 1;
+                        cacheOracle.verifyRequests(session.requests, session.responses, firstIndex, expectedCount, "filterBack requests", backwards);
+                    }
+                    finished();
+                });
+            }
+            else {
+                cache.filterForward(index, count, predicate).then(function (actualResults) {
+                    var expectedResults = CacheOracle.getExpectedFilterResults(expectData, index, count, predicate, backwards)
+                    djstest.assertAreEqualDeep(actualResults, expectedResults, "results for " + "filterForward requests");
+
+                    if (session && cacheOracle) {
+                        if (expectedResults.value.length > 0) {
+                            // If the count is not satisfied in the expected results, read to the end of the collection
+                            // otherwise read to the last index
+                            var lastIndex = (expectedResults.value.length < count) ? itemsInCollection : expectedResults.value[expectedResults.value.length - 1].index + 1;
+                            // One request is made if the index is outside the range of the collection if the end of the collection has not yet been found
+                            var expectedCount = (index < itemsInCollection) ? (lastIndex - index) : 1;
+                        }
+                        else {
+                            var expectedCount = itemsInCollection;
+                        }
+
+                        cacheOracle.verifyRequests(session.requests, session.responses, index, expectedCount, "filterForward requests", backwards);
+                    }
+                    finished();
+                });
+            }
+        });
+    };
+
+    var createMultipleFilterTestName = function (scenarioName, params) {
+        return "Testing " + scenarioName + (params.backwards ? "filterBack: " : "filterForward: ") + " of " + params.feed + " with predicate " + params.predicate + " [index " +
+            params.firstIndex + ", count " + params.firstCount + "] and [index " + params.secondIndex + ", count " + params.secondCount +
+            "] with pageSize " + params.pageSize + ", and prefetch " + params.prefetchSize;
+    };
+
+    var createSingleFilterTestName = function (scenarioName, params) {
+        return "Testing " + scenarioName + (params.backwards ? "filterBack: " : "filterForward: ") + " of " + params.feed + " with predicate " + params.predicate + " [index " +
+            params.index + ", count " + params.count + "] with pageSize " + params.pageSize + ", and prefetch " + params.prefetchSize;
+    };
+
+    var singleFilterTest = function (params) {
+        djstest.assertsExpected(2);
+        var options = { name: "cache" + new Date().valueOf(), source: params.feed, pageSize: params.pageSize, prefetchSize: params.prefetchSize };
+
+        var cache = datajs.createDataCache(options);
+        var cacheOracle = new CacheOracle(params.feed, params.pageSize, itemsInCollection);
+        var session = this.observableHttpClient.newSession();
+        validateFilterResultsAndRequests(params.feed, cache, params.index, params.count, params.predicate, function () { djstest.destroyCacheAndDone(cache) }, params.backwards, session, cacheOracle);
+    };
+
+    var filterAfterReadRangeTest = function (params) {
+        djstest.assertsExpected(3);
+        var options = { name: "cache" + new Date().valueOf(), source: params.feed, pageSize: params.pageSize, prefetchSize: params.prefetchSize };
+
+        var cache = datajs.createDataCache(options);
+        var cacheOracle = new CacheOracle(params.feed, params.pageSize, itemsInCollection);
+        var session = this.observableHttpClient.newSession();
+
+        cache.readRange(params.skip, params.take).then(function (data) {
+            cacheOracle.verifyRequests(session.requests, session.responses, params.skip, params.take, "readRange requests");
+            session.clear();
+            validateFilterResultsAndRequests(params.feed, cache, params.index, params.count, params.predicate, function () { djstest.destroyCacheAndDone(cache); }, params.backwards, session, cacheOracle);
+        });
+    };
+
+    var parallelFilterTest = function (params) {
+        djstest.assertsExpected(2);
+        var options = { name: "cache" + new Date().valueOf(), source: params.feed, pageSize: params.pageSize, prefetchSize: params.prefetchSize };
+
+        var cache = datajs.createDataCache(options);
+
+        var firstfilter = function (finished) {
+            validateFilterResultsAndRequests(params.feed, cache, params.firstIndex, params.firstCount, params.predicate, finished, params.backwards);
+        };
+
+        var secondfilter = function (finished) {
+            validateFilterResultsAndRequests(params.feed, cache, params.secondIndex, params.secondCount, params.predicate, finished, params.backwards);
+        };
+
+        djstest.asyncDo([firstfilter, secondfilter], function () {
+            djstest.destroyCacheAndDone(cache);
+        });
+    };
+
+    var serialFilterTest = function (params) {
+        djstest.assertsExpected(4);
+        var options = { name: "cache" + new Date().valueOf(), source: params.feed, pageSize: params.pageSize, prefetchSize: params.prefetchSize };
+
+        var cache = datajs.createDataCache(options);
+        var cacheOracle = new CacheOracle(params.feed, params.pageSize, itemsInCollection);
+        var session = this.observableHttpClient.newSession();
+
+        var filterMethod = function (index, count, predicate, backwards) {
+            if (backwards) {
+                return cache.filterBack(index, count, predicate);
+            }
+            else {
+                return cache.filterForward(index, count, predicate)
+            }
+        }
+
+        filterMethod(params.firstIndex, params.firstCount, params.predicate, params.backwards).then(
+            function (results) {
+                validateFilterResultsAndRequests(params.feed, cache, params.firstIndex, params.firstCount, params.predicate,
+                function () {
+                    session.clear();
+                    validateFilterResultsAndRequests(params.feed, cache, params.secondIndex, params.secondCount, params.predicate, function () { djstest.destroyCacheAndDone(cache) }, params.backwards, session, cacheOracle);
+                }, params.backwards, session, cacheOracle);
+            });
+    };
+
+    module("Functional", {
+        setup: function () {
+            this.observableHttpClient = new ObservableHttpClient();
+            OData.defaultHttpClient = this.observableHttpClient;
+        },
+
+        teardown: function () {
+            OData.defaultHttpClient = this.observableHttpClient.provider;
+        }
+    });
+
+    $.each(filterPredicates, function (_, filterPredicate) {
+        $.each(feeds, function (_, feedObject) {
+            $.each(filterForwardAfterReadCounts, function (_, filterCount) {
+                $.each(filterForwardAfterReadIndices, function (_, filterIndex) {
+                    var parameters = { index: filterIndex, count: filterCount, predicate: filterPredicate, feed: feedObject.feed, take: readRangeTake,
+                        skip: readRangeStart, pageSize: pageSize, prefetchSize: 0, backwards: false
+                    };
+                    djstest.addTest(filterAfterReadRangeTest, createSingleFilterTestName("after readRange, ", parameters), parameters);
+                });
+            });
+
+            $.each(filterBackAfterReadCounts, function (_, filterCount) {
+                $.each(filterBackAfterReadIndices, function (_, filterIndex) {
+                    var parameters = { index: filterIndex, count: filterCount, predicate: filterPredicate, feed: feedObject.feed, take: readRangeTake,
+                        skip: readRangeStart, pageSize: pageSize, prefetchSize: 0, backwards: true
+                    };
+                    djstest.addTest(filterAfterReadRangeTest, createSingleFilterTestName("After readRange, ", parameters), parameters);
+                });
+            });
+        });
+
+        $.each(singleFilterForwardIndices, function (_, filterIndex) {
+            $.each(singleFilterForwardCounts, function (_, filterCount) {
+                var parameters = { index: filterIndex, count: filterCount, predicate: filterPredicate, feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 0, backwards: false };
+                djstest.addTest(singleFilterTest, createSingleFilterTestName("single ", parameters), parameters);
+            });
+        });
+
+        $.each(singleFilterBackIndices, function (_, filterIndex) {
+            $.each(singleFilterBackCounts, function (_, filterCount) {
+                var parameters = { index: filterIndex, count: filterCount, predicate: filterPredicate, feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 0, backwards: true };
+                djstest.addTest(singleFilterTest, createSingleFilterTestName("single ", parameters), parameters);
+            });
+        });
+
+        $.each(multipleFilterForwards, function (_, firstFilter) {
+            $.each(multipleFilterForwards, function (_, secondFilter) {
+                var serialParameters = { firstIndex: firstFilter.index, firstCount: firstFilter.count, secondIndex: secondFilter.index, secondCount: secondFilter.count,
+                    predicate: filterPredicate, feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 0, backwards: false
+                };
+                djstest.addTest(serialFilterTest, createMultipleFilterTestName("serial ", serialParameters), serialParameters);
+            });
+        });
+
+        $.each(multipleFilterBacks, function (_, firstFilter) {
+            $.each(multipleFilterBacks, function (_, secondFilter) {
+                var serialParameters = { firstIndex: firstFilter.index, firstCount: firstFilter.count, secondIndex: secondFilter.index, secondCount: secondFilter.count,
+                    predicate: filterPredicate, feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 0, backwards: true
+                };
+                djstest.addTest(serialFilterTest, createMultipleFilterTestName("serial ", serialParameters), serialParameters);
+            });
+        });
+
+        $.each(multipleFilterForwards, function (_, firstFilter) {
+            $.each(multipleFilterForwards, function (_, secondFilter) {
+                var parallelParameters = { firstIndex: firstFilter.index, firstCount: firstFilter.count, secondIndex: secondFilter.index, secondCount: secondFilter.count,
+                    predicate: filterPredicate, feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 6, backwards: false
+                };
+                djstest.addTest(parallelFilterTest, createMultipleFilterTestName("parallel ", parallelParameters), parallelParameters);
+            });
+        });
+
+        $.each(multipleFilterBacks, function (_, firstFilter) {
+            $.each(multipleFilterBacks, function (_, secondFilter) {
+                var parallelParameters = { firstIndex: firstFilter.index, firstCount: firstFilter.count, secondIndex: secondFilter.index, secondCount: secondFilter.count,
+                    predicate: filterPredicate, feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 6, backwards: true
+                };
+                djstest.addTest(parallelFilterTest, createMultipleFilterTestName("parallel ", parallelParameters), parallelParameters);
+            });
+        });
+
+        $.each([true, false], function (_, isBackwards) {
+            var zeroCountParameters = { index: 0, count: 0, predicate: filterPredicate, feed: feeds[0].feed, take: readRangeTake,
+                skip: readRangeStart, pageSize: pageSize, prefetchSize: 0, backwards: isBackwards
+            };
+            djstest.addTest(singleFilterTest, createSingleFilterTestName("Count 0 ", zeroCountParameters), zeroCountParameters);
+        });
+    });
+
+    $.each([true, false], function (_, backwards) {
+        $.each(invalidIndices, function (_, invalidIndex) {
+            var invalidIndexParameters = { index: invalidIndex, count: -1, predicate: filterPredicates[0], feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 0, backwards: backwards };
+
+            djstest.addTest(
+                function (params) {
+                    djstest.assertsExpected(1);
+                    var options = { name: "cache" + new Date().valueOf(), source: params.feed };
+                    var cache = datajs.createDataCache(options);
+                    try {
+                        params.backwards ?
+                            cache.filterForward(params.index, params.count, params.predicate).then(function (results) {
+                                djstest.log(results);
+                            }) :
+                            cache.filterForward(params.index, params.count, params.predicate).then(function (results) {
+                                djstest.log(results);
+                            });
+                        expectException(cache);
+                    } catch (e) {
+                        djstest.assertAreEqual(e.message, "'index' must be a valid number.", "Error message validation");
+                        djstest.destroyCacheAndDone(cache);
+                    }
+                }, createSingleFilterTestName("invalid index ", invalidIndexParameters), invalidIndexParameters);
+        });
+
+        $.each(invalidCounts, function (_, invalidCount) {
+            var invalidCountParameters = { index: 0, count: invalidCount, predicate: filterPredicates[0], feed: feeds[0].feed, pageSize: pageSize, prefetchSize: 0, backwards: backwards };
+
+            djstest.addTest(
+                function (params) {
+                    djstest.assertsExpected(1);
+                    var options = { name: "cache" + new Date().valueOf(), source: params.feed };
+                    var cache = datajs.createDataCache(options);
+                    try {
+                        params.backwards ?
+                            cache.filterBack(params.index, params.count, params.predicate).then(function (results) {
+                                djstest.log(results);
+                            }) :
+                            cache.filterForward(params.index, params.count, params.predicate).then(function (results) {
+                                djstest.log(results);
+                            });
+                        expectException(cache);
+                    } catch (e) {
+                        djstest.assertAreEqual(e.message, "'count' must be a valid number.", "Error message validation");
+                        djstest.destroyCacheAndDone(cache);
+                    }
+                }, createSingleFilterTestName("invalid count ", invalidCountParameters), invalidCountParameters);
+        });
+    });
+})(this);


[10/10] git commit: [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
[OLINGO-276] Check in the missing test source codes.


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/commit/48761e07
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/tree/48761e07
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/diff/48761e07

Branch: refs/heads/master
Commit: 48761e07fed897bc68496b8b526dc9ab6c71088c
Parents: 1b51dcb
Author: Bing Li <bi...@apache.org>
Authored: Thu May 15 09:07:47 2014 +0800
Committer: Bing Li <bi...@apache.org>
Committed: Thu May 15 09:07:47 2014 +0800

----------------------------------------------------------------------
 JSLib/packages.config                           |    8 +
 JSLib/tests/cache-tests.js                      | 1191 +++++++++
 JSLib/tests/common/CacheOracle.js               |  228 ++
 JSLib/tests/common/Instrument.js                |   48 +
 JSLib/tests/common/Instrument.svc               |   71 +
 JSLib/tests/common/ObservableHttpClient.js      |   78 +
 JSLib/tests/common/gpo-ie8-tour-disable.reg     |  Bin 0 -> 384 bytes
 JSLib/tests/common/mockHttpClient.js            |  107 +
 JSLib/tests/common/mockXMLHttpRequest.js        |  192 ++
 JSLib/tests/common/rx.js                        |    6 +
 ...cache-large-collection-functional-tests.html |   53 +
 ...s-cache-large-collection-functional-tests.js |  170 ++
 JSLib/tests/datajs-cache-long-haul-tests.html   |  192 ++
 JSLib/tests/datajs-startup-perf-test.html       |   89 +
 JSLib/tests/endpoints/BasicAuthDataService.svc  |  108 +
 JSLib/tests/endpoints/CustomAnnotations.xml     |  102 +
 JSLib/tests/endpoints/CustomDataService.svc     |   76 +
 JSLib/tests/endpoints/EpmDataService.svc        |  315 +++
 JSLib/tests/endpoints/ErrorDataService.svc      |   56 +
 .../tests/endpoints/LargeCollectionService.svc  |   93 +
 JSLib/tests/odata-batch-functional-tests.html   |   43 +
 JSLib/tests/odata-batch-functional-tests.js     |  270 ++
 JSLib/tests/odata-batch-tests.js                |  551 ++++
 .../odata-cache-filter-functional-tests.html    |   56 +
 .../odata-cache-filter-functional-tests.js      |  416 +++
 JSLib/tests/odata-cache-fperf-tests.html        |   54 +
 JSLib/tests/odata-cache-fperf-tests.js          |  103 +
 JSLib/tests/odata-cache-functional-tests.html   |   56 +
 JSLib/tests/odata-cache-functional-tests.js     |  611 +++++
 .../tests/odata-cache-rx-functional-tests.html  |   54 +
 JSLib/tests/odata-cache-rx-functional-tests.js  |   74 +
 JSLib/tests/odata-fuzz.html                     |  561 ++++
 JSLib/tests/odata-handler-tests.js              |  319 +++
 JSLib/tests/odata-json-light-tests.js           | 2479 ++++++++++++++++++
 JSLib/tests/odata-json-tests.js                 |  888 +++++++
 JSLib/tests/odata-links-functional-tests.html   |   44 +
 JSLib/tests/odata-links-functional-tests.js     |  232 ++
 ...ata-metadata-awareness-functional-tests.html |   43 +
 ...odata-metadata-awareness-functional-tests.js |  227 ++
 JSLib/tests/odata-metadata-tests.js             |  486 ++++
 JSLib/tests/odata-net-tests.js                  |  286 ++
 JSLib/tests/odata-perf-tests.html               |   45 +
 JSLib/tests/odata-perf-tests.js                 |  229 ++
 ...odata-read-crossdomain-functional-tests.html |   53 +
 .../odata-read-crossdomain-functional-tests.js  |  137 +
 JSLib/tests/odata-read-functional-tests.html    |   45 +
 JSLib/tests/odata-read-functional-tests.js      |  583 ++++
 JSLib/tests/odata-request-functional-tests.html |   44 +
 JSLib/tests/odata-request-functional-tests.js   |  386 +++
 JSLib/tests/odata-roundtrip-functional-tests.js |  374 +++
 JSLib/tests/odata-tests.js                      |  306 +++
 JSLib/tests/odata-xml-tests.js                  |  259 ++
 JSLib/tests/store-indexeddb-tests.js            |  246 ++
 JSLib/tests/store-tests.js                      |  684 +++++
 JSLib/tests/test-manager.html                   |   88 +
 55 files changed, 14515 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/packages.config
----------------------------------------------------------------------
diff --git a/JSLib/packages.config b/JSLib/packages.config
new file mode 100644
index 0000000..d2b5a9b
--- /dev/null
+++ b/JSLib/packages.config
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.OData.Client" version="6.0.0-beta1" targetFramework="net40" />
+  <package id="Microsoft.OData.Core" version="6.0.0-beta1" targetFramework="net40" />
+  <package id="Microsoft.OData.Edm" version="6.0.0-beta1" targetFramework="net40" />
+  <package id="Microsoft.OData.Service" version="6.0.0-beta1" targetFramework="net40" />
+  <package id="Microsoft.Spatial" version="6.0.0-beta1" targetFramework="net40" />
+</packages>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/cache-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/cache-tests.js b/JSLib/tests/cache-tests.js
new file mode 100644
index 0000000..13fd4b9
--- /dev/null
+++ b/JSLib/tests/cache-tests.js
@@ -0,0 +1,1191 @@
+/// <reference path="../src/datajs.js" />
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/cache.js" />
+/// <reference path="common/djstest.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// cache-tests.js
+
+(function (window, undefined) {
+
+    module("Unit");
+    var foodsFeed = "./endpoints/FoodStoreDataServiceV4.svc/Foods";
+    var collectionSize = 16;
+
+    var thenFailTest = function (err) {
+        if (err && err.message) {
+            djstest.fail(err.message);
+        } else {
+            djstest.fail("unexepected promise failure");
+        }
+
+        djstest.done();
+    };
+
+    djstest.addTest(function dataCacheCountTest() {
+        var cache = datajs.createDataCache({ name: "cache", source: foodsFeed });
+        cache.count().then(function (count) {
+            djstest.assertAreEqual(count, collectionSize, "expected count for Foods");
+            djstest.destroyCacheAndDone(cache);
+        }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheCountOnLocalTest() {
+        var cache = datajs.createDataCache({ name: "cache", source: foodsFeed, pageSize: collectionSize + 10, mechanism: "memory" });
+        cache.readRange(0, collectionSize + 10).then(function (data) {
+            var expectedCount = data.value ? data.value.length : 0;
+            cache.count().then(function (count) {
+                djstest.assertAreEqual(count, expectedCount, "expected count for expectedCount");
+                djstest.destroyCacheAndDone(cache);
+            }, thenFailTest);
+        }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheCountAbortTest() {
+        // Abort before completion.
+        var cache = datajs.createDataCache({ name: "cache", source: foodsFeed });
+        var item = cache.count().then(thenFailTest, function (err) {
+            djstest.assertAreEqual(true, err.canceled, "err.aborted is true");
+            djstest.destroyCacheAndDone(cache);
+        }).cancel();
+    });
+
+    var createNewCache = function (options) {
+
+        var thenSuccess = null;
+
+        var resolved = false;
+        var rejected = false;
+
+        var args = null;
+
+        this.then = function (success) {
+            if (resolved) {
+                success.apply(null, args);
+            } else if (!rejected) {
+                thenSuccess = success;
+            }
+        };
+
+        var resolve = function (/*args*/) {
+            resolved = true;
+            args = arguments;
+            if (thenSuccess) {
+                thenSuccess.apply(null, arguments);
+            }
+        };
+
+        var cache = datajs.createDataCache(options);
+        cache.clear().then(function () {
+            var newCache = datajs.createDataCache(options);
+            resolve(newCache);
+        }, function (err) {
+            rejected = true;
+            thenFailTest(err);
+        });
+
+        return this;
+    };
+
+    djstest.addTest(function dataCacheReadRangeSingleTest() {
+        // Read a new range.
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        createNewCache(options).
+            then(function (cache) {
+                cache.readRange(0, 1).
+                    then(function (data) {
+                        djstest.assertAreEqual(data.value.length, 1, "single item was read.");
+                        djstest.assertAreEqual(data.value[0].FoodID, 0, "food id is 0.");
+                        djstest.done();
+                    }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadRangeExactPageTest() {
+        // Read exactly one page.
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        createNewCache(options).
+            then(function (cache) {
+                cache.readRange(0, 2).
+                    then(function (data) {
+                        djstest.assertAreEqual(data.value.length, 2, "single item was read.");
+                        djstest.assertAreEqual(data.value[0].FoodID, 0, "first food id is 0.");
+                        djstest.assertAreEqual(data.value[1].FoodID, 1, "second food id is 1.");
+                        djstest.done();
+                    }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadRangeMultiplePageTest() {
+        // Read multiple pages.
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        createNewCache(options).
+            then(function (cache) {
+                cache.readRange(0, 3).
+                    then(function (data) {
+                        djstest.assertAreEqual(data.value.length, 3, "single item was read.");
+                        djstest.assertAreEqual(data.value[0].FoodID, 0, "first food id is 0.");
+                        djstest.assertAreEqual(data.value[1].FoodID, 1, "second food id is 1.");
+                        djstest.assertAreEqual(data.value[2].FoodID, 2, "third food id is 2.");
+                        djstest.done();
+                    }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadRangeSyncDelaysTest() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        var counter = 0;
+        createNewCache(options).
+            then(function (cache) {
+                cache.readRange(0, 1).
+                    then(function (data) {
+                        djstest.assertAreEqual(counter, 0, "counter is zero for first set of results");
+                        djstest.assertAreEqual(cache.stats.netReads, 1, "one request made to fulfill the readRange");
+                        counter++;
+                        cache.readRange(0, 1).
+                            then(function (data) {
+                                djstest.assertAreEqual(counter, 2, "counter is two because even sync results are delayed)");
+                                djstest.assertAreEqual(cache.stats.netReads, 1, "no additional requests since requested data is in cache");
+                                djstest.done();
+                            }, thenFailTest);
+                        djstest.assertAreEqual(counter, 1, "counter is one because readRange hasn't run (even if results are cached)");
+                        counter++;
+                    }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadRangesWithDestroyTest() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 0 };
+        var counter = 0;
+        createNewCache(options).then(function (cache) {
+            cache.readRange(0, 1).then(function (data) {
+                djstest.assertAreEqual(cache.stats.netReads, 1, "one request made to fulfill the readRange");
+                cache.clear().then(function () {
+                    djstest.assertAreEqual(cache.stats.netReads, 0, "stats cleared in destroy");
+                    cache.readRange(0, 1).then(function (data) {
+                        djstest.assertAreEqual(cache.stats.netReads, 1, "request made after destroy to fulfill the readRange");
+                        djstest.done();
+                    }, thenFailTest);
+                }, thenFailTest);
+            }, thenFailTest);
+        }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadSimultaneousTest() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        var counter = 0;
+        var theCache;
+        var checkDataAndCount = function (data) {
+            djstest.assertAreEqual(data.value.length, 1, "Single item returned");
+            djstest.assertAreEqual(data.value[0].FoodID, 0, "FoodId is set to zero for first item");
+            djstest.assertAreEqual(theCache.stats.netReads, 1, "single theCache.stats.netReads");
+            djstest.assert(theCache.stats.prefetches <= 1, "theCache.stats.prefetches <= 1 - no repetitions");
+            counter++;
+            if (counter === 3) {
+                djstest.done();
+            }
+        };
+
+        createNewCache(options).
+            then(function (cache) {
+                theCache = cache;
+                cache.readRange(0, 1).then(checkDataAndCount, thenFailTest);
+                cache.readRange(0, 1).then(checkDataAndCount, thenFailTest);
+                cache.readRange(0, 1).then(checkDataAndCount, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheMultipleClearTest() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        var counter = 0;
+        var checkCount = function (data) {
+            djstest.assert(true, "clear then was called");
+            counter++;
+            if (counter === 3) {
+                djstest.done();
+            }
+        };
+
+        createNewCache(options).
+            then(function (cache) {
+                cache.readRange(0, 1).then(function () {
+                    cache.clear().then(checkCount, thenFailTest);
+                    cache.clear().then(checkCount, thenFailTest);
+                    cache.clear().then(checkCount, thenFailTest);
+                }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheOnIdleIsFired() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 0 };
+
+        createNewCache(options).
+            then(function (cache) {
+                var counter = 0;
+                var clearSucceeded = false;
+
+                var thenFailThisTest = function (err) {
+                    if (err && err.message) {
+                        djstest.fail(err.message);
+                    } else {
+                        djstest.fail("unexepected promise failure");
+                    }
+                };
+
+                cache.onidle = function () {
+                    counter++;
+                    djstest.assertAreEqual(counter, 1, "onidle was called 1 times");
+                    djstest.assert(clearSucceeded, "onidle was called after destroy");
+                    djstest.done();
+                };
+
+                cache.readRange(0, 1).then(null, thenFailThisTest);
+                cache.readRange(0, 1).then(null, thenFailThisTest);
+                cache.readRange(3, 4).then(function () {
+                    cache.readRange(5, 6).then(function () {
+                        cache.clear().then(function () {
+                            clearSucceeded = true;
+                        }, thenFailThisTest);
+                    }, thenFailThisTest);
+                }, thenFailThisTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheOnIdleFiresOnErrorTest() {
+
+        var errorResponse = false;
+        var httpClient = {
+            request: function (request, success, error) {
+                var response = { statusCode: 200, headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, body: JSON.stringify({ d: [1, 2] }) };
+                if (!errorResponse) {
+                    errorResponse = true;
+                    setTimeout(function () {
+                        success(response);
+                    }, 0);
+                } else {
+                    response.statusCode = 500;
+                    response.body = "bad response";
+                    setTimeout(function () {
+                        error({ message: "HTTP request failed", request: request, response: response });
+                    }, 0);
+                }
+            }
+        };
+
+        var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 0, httpClient: httpClient };
+
+        createNewCache(options).
+            then(function (cache) {
+                var counter = 0;
+                var errorHandlerCalled = false;
+
+                var thenFailThisTest = function (err) {
+                    if (err && err.message) {
+                        if (errorResponse) {
+                            djstest.assertAreEqual(err.message, "HTTP request failed", "Error is the expected one");
+                            errorHandlerCalled = true;
+                        } else {
+                            djstest.fail(err.message);
+                        }
+                    } else {
+                        djstest.fail("unexepected promise failure");
+                    }
+                };
+
+                cache.onidle = function () {
+                    counter++;
+                    djstest.assertAreEqual(counter, 1, "onidle was called");
+                    djstest.assert(errorHandlerCalled, "error handler was called before onidle");
+                    cache.onidle = null;
+                    cache.clear().then(function () {
+                        djstest.done();
+                    }, thenFailTest);
+                };
+                cache.readRange(0, 2).then(function () {
+                    cache.readRange(2, 4).then(function () {
+                        djstest.fail("unexpected readRange success");
+                    }, thenFailThisTest);
+                }, thenFailThisTest);
+            }, thenFailTest);
+    });
+
+
+    djstest.addTest(function dataCacheOdataSourceNormalizedURITest() {
+        var requestsMade = 0;
+        var httpClient = {
+            request: function (request, success, error) {
+                var response = { statusCode: 200, headers: { "Content-Type": "application/json", "OData-Version": "4.0" }, body: JSON.stringify({ value: [1, 2] }) };
+                if (requestsMade === 0) {
+                    djstest.pass("Cache made first request for data from the source");
+                    requestsMade++;
+                } else {
+                    // In memory storage will make a second request from the new cache since two caches with the same name will coexist
+                    if (window.mozIndexedDB || window.localStorage || requestsMade > 1) {
+                        djstest.fail("Unexpected request to the source");
+                    } else {
+                        djstest.pass("In memory cache requested the data from the source");
+                    }
+                }
+                setTimeout(function () {
+                    success(response);
+                }, 0);
+            }
+        };
+
+        var options = { name: "cache", source: "http://exampleuri.com/my service.svc", pageSize: 2, prefetchSize: 0, httpClient: httpClient };
+
+        createNewCache(options).
+            then(function (cache) {
+                cache.readRange(0, 2).then(function () {
+                    options.source = "HtTp://ExampleURI.cOm/my%20service.svc";
+                    var newCache = datajs.createDataCache(options);
+                    newCache.readRange(0, 2).then(function (data) {
+                        djstest.assertAreEqualDeep(data.value, [1, 2], "Got the expected data from the new cache instance");
+                        newCache.clear().then(function () {
+                            djstest.done();
+                        }, thenFailTest);
+                    }, thenFailTest);
+                }, thenFailTest);
+            }, thenFailTest);
+    });
+
+
+    djstest.addTest(function dataCachePrefetchAllTest() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: -1 };
+        var counter = 0;
+        var theCache;
+
+        var callback = function () {
+            counter++;
+            if (counter === 2) {
+                djstest.assertAreEqual(1, theCache.stats.netReads, "single page to satisfy read (" + theCache.stats.netReads + ")");
+                djstest.assert(theCache.stats.prefetches > 1, "theCache.stats.prefetches(" + theCache.stats.prefetches + ") > 1 - multiple prefetches");
+                djstest.done();
+            }
+        };
+
+        var checkDataAndCount = function (data) {
+            djstest.assertAreEqual(data.value.length, 1, "Single item returned");
+            djstest.assertAreEqual(data.value[0].FoodID, 0, "FoodId is set to zero for first item");
+            djstest.assertAreEqual(1, theCache.stats.netReads, "single theCache.stats.netReads");
+            djstest.assert(theCache.stats.prefetches <= 1, "theCache.stats.prefetches <= 1 - no repetitions");
+            callback();
+        };
+
+        createNewCache(options).
+            then(function (cache) {
+                theCache = cache;
+                cache.readRange(0, 1).then(checkDataAndCount, thenFailTest);
+                cache.onidle = function () {
+                    djstest.log("onidle fired");
+                    callback();
+                };
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadRangeTakeMoreThanCollectionCountTest() {
+        // Read multiple pages.
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        createNewCache(options).
+            then(function (cache) {
+                cache.count().then(function (count) {
+                    cache.readRange(0, count + 1).
+                        then(function (data) {
+                            djstest.assertAreEqual(data.value.length, count, "all items in the collection were read.");
+                            cache.readRange(2, count + 1).
+                                then(function (data) {
+                                    djstest.assertAreEqual(data.value.length, count - 2, "all requested in the collection were read.");
+                                    djstest.assertAreEqual(data.value[0].FoodID, 2, "first read food id is 2.");
+                                    djstest.done();
+                                }, thenFailTest);
+                        }, thenFailTest);
+                }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadRangeSkipMoreThanCollectionCountTest() {
+        // Read multiple pages.
+        var options = { name: "cache", source: foodsFeed, pageSize: 2 };
+        createNewCache(options).
+            then(function (cache) {
+                cache.count().then(function (count) {
+                    cache.readRange(count + 1, 5).
+                        then(function (data) {
+                            djstest.assertAreEqual(data.value.length, 0, "no items were read.");
+                            djstest.done();
+                        }, thenFailTest);
+                }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheReadRangeTakeMoreThanPrefetchSizeTest() {
+        // Read multiple pages.
+        var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 1 };
+        createNewCache(options).
+            then(function (cache) {
+                cache.readRange(0, 4).
+                        then(function (data) {
+                            djstest.assertAreEqual(data.value.length, 4, "all requested in the collection were read.");
+                            djstest.assertAreEqual(data.value[0].FoodID, 0, "first food id is 0.");
+                            djstest.assertAreEqual(data.value[1].FoodID, 1, "second food id is 1.");
+                            djstest.assertAreEqual(data.value[2].FoodID, 2, "third food id is 2.");
+                            djstest.assertAreEqual(data.value[3].FoodID, 3, "third food id is 3.");
+                            djstest.done();
+                        }, thenFailTest);
+            }, thenFailTest);
+    });
+
+    djstest.addTest(function dataCacheRangeInvalidIndexAndCount() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 2, prefetchSize: 1 };
+        var counter = 0;
+
+        var thenFailSuccess = function () {
+            djstest.fail("Call to success was unexpected");
+            counter++;
+            if (counter === tests.length) {
+                djstest.done();
+            }
+        };
+
+        var thenFailError = function () {
+            djstest.fail("Call to error was unexpected");
+            counter++;
+            if (counter === tests.length) {
+                djstest.done();
+            }
+        };
+
+        var invalidValues = [-5, -1, null, undefined, NaN, Infinity, "5", "this is not a number"];
+        var tests = [];
+        $.each(invalidValues, function (_, value) {
+            tests.push({ i: value, c: 0 });
+            tests.push({ i: 0, c: value });
+            tests.push({ i: value, c: value });
+        });
+
+        createNewCache(options).
+            then(function (cache) {
+                var i, len;
+                for (i = 0, len = tests.length; i < len; i++) {
+                    var test = tests[i];
+                    try {
+                        cache.readRange(test.i, test.c).then(thenFailSuccess, thenFailTest);
+                    } catch (err) {
+                        djstest.pass("Expected exception was thrown: " + err.message);
+                        counter++;
+                    }
+                }
+                if (counter === tests.length) {
+                    djstest.done();
+                }
+            });
+    });
+
+
+    djstest.addTest(function cacheOptionsForCountTest() {
+        var httpClient = {
+            request: function (r, success, error) {
+                window.setTimeout(function () {
+                    success({ data: "10" });
+                }, 1);
+                return null;
+            }
+        };
+        var cache = datajs.createDataCache({
+            name: "mem", mechanism: "memory", source: "http://www.example.org/service/",
+            httpClient: httpClient
+        });
+        cache.count().then(function (count) {
+            djstest.assertAreEqual(count, 10, "count value");
+            djstest.done();
+        }, djstest.failAndDoneCallback("failed to get count"));
+    });
+
+    djstest.addTest(function dataCacheDestoryStopsThePrefetcherTest() {
+        var oldHttpClientRequest = OData.defaultHttpClient.request;
+        var prefetchCount = 0;
+        var theCache;
+
+        OData.defaultHttpClient.request = function (request, success, error) {
+            prefetchCount++;
+            djstest.assert(prefetchCount <= 3, "Expected prefetch request");
+            if (prefetchCount === 3) {
+                theCache.clear().then(function () {
+                    djstest.assertAreEqual(prefetchCount, 3, "cache.clear() stopped the prefetcher");
+                    djstest.done();
+                    OData.defaultHttpClient.request = oldHttpClientRequest;
+                }, thenFailTest);
+                return {
+                    abort: function () { }
+                };
+            }
+            return oldHttpClientRequest(request, success, error);
+        };
+
+        try {
+            var options = { name: "cache", source: foodsFeed, pageSize: 1, prefetchSize: -1 };
+            createNewCache(options).then(function (cache) {
+                theCache = cache;
+                cache.readRange(0, 0).then(null, thenFailTest);
+            });
+        } catch (e) {
+            OData.defaultHttpClient.request = oldHttpClientRequest;
+            djstest.fail("Exception thrown,  prefetchSize: " + tests[count] + " error:  " + e.message);
+            djstest.done();
+        }
+    });
+
+    djstest.addTest(function dataCacheFilterTest() {
+        var options = { name: "cache", source: foodsFeed, pageSize: 3, prefetchSize: -1 };
+        var counter = 0;
+
+        var singleItemPredicate = function (data) {
+            return data.FoodID === 2;
+        };
+
+        var multipleItemPredicate = function (data) {
+            return data.FoodID % 2 === 1;
+        };
+
+        var noItemPredicate = function (data) {
+            return data.Name === "something i would never eat";
+        };
+
+        var allItemPredicate = function (data) {
+            return data.FoodID >= 0;
+        };
+
+        var doneAfterAllTests = function () {
+            counter++;
+            if (counter === tests.length) {
+                djstest.done();
+            }
+        };
+
+        var last = collectionSize - 1;
+        var tests = [
+            { index: 0, count: -5, predicate: singleItemPredicate },    // Single match in entire collection
+            {index: 2, count: 1, predicate: singleItemPredicate },     // Single match, single count
+            {index: 3, count: 1, predicate: singleItemPredicate },     // Single match skipped, i.e. no matches
+            {index: 0, count: -1, predicate: multipleItemPredicate },  // Multiple matches in entire collection
+            {index: 0, count: 5, predicate: multipleItemPredicate },   // Multiple matches, take partial
+            {index: 3, count: 5, predicate: multipleItemPredicate },   // Multiple matches, skip/take partial
+            {index: 7, count: 10, predicate: multipleItemPredicate },  // Multiple matches, skip partial, take past end of collection
+            {index: 13, count: 4, predicate: allItemPredicate },       // All items match, skip/take partial
+            {index: 0, count: 20, predicate: noItemPredicate },        // No matches
+            {index: 0, count: 0, predicate: allItemPredicate },        // Zero count
+            {index: -5, count: 1, predicate: allItemPredicate },       // Negative index
+            {index: last + 1, count: 1, predicate: allItemPredicate }, // Index past end of collection
+
+            {index: last, count: -5, predicate: singleItemPredicate, backwards: true },        // Single match in entire collection
+            {index: 2, count: 1, predicate: singleItemPredicate, backwards: true },            // Single match, single count
+            {index: 1, count: 1, predicate: singleItemPredicate, backwards: true },            // Single match skipped, i.e. no matches
+            {index: last, count: -1, predicate: multipleItemPredicate, backwards: true },      // Multiple matches in entire collection
+            {index: last, count: 6, predicate: multipleItemPredicate, backwards: true },       // Multiple matches, take partial
+            {index: last - 3, count: 5, predicate: multipleItemPredicate, backwards: true },   // Multiple matches, skip/take partial
+            {index: 13, count: 10, predicate: multipleItemPredicate, backwards: true },        // Multiple matches, skip partial, take past end of collection
+            {index: 4, count: 13, predicate: allItemPredicate, backwards: true },              // All items match, skip/take partial
+            {index: last, count: 20, predicate: noItemPredicate, backwards: true },            // No matches
+            {index: 0, count: 0, predicate: allItemPredicate, backwards: true },               // Zero count
+            {index: -5, count: 1, predicate: allItemPredicate, backwards: true },              // Negative index
+            {index: last + 1, count: 1, predicate: allItemPredicate, backwards: true },        // Index past end of collection
+
+            {index: "foo", count: 1, predicate: singleItemPredicate, exception: { message: "'index' must be a valid number.", index: NaN} },
+            { index: 0, count: "foo", predicate: multipleItemPredicate, exception: { message: "'count' must be a valid number.", count: NaN} }
+        ];
+
+        var testDescription = function(test) {
+            return "filter [" + test.index + ", " + test.count + " " + (test.backwards ? "back" : "forward") + "] for predicate " + test.predicate;
+        };
+
+        var filterErrorCallback = function (err) {
+            if (err && err.message) {
+                djstest.fail(err.message);
+            } else {
+                djstest.fail("unexpected promise failure");
+            }
+            doneAfterAllTests();
+        };
+
+        var removeSafariExceptionProperties = function (err) {
+            /// <summary>Removes Safari-specific properties from an exception object</summary>
+            /// <params name="err" type="Exception">The exception object to operate on</param>
+            /// <returns type="Exception">The original exception object with the Safari-specific properties removed</returns>
+            var safariProperties = ["line", "expressionBeginOffset", "expressionEndOffset", "sourceId", "sourceURL"];
+
+            var result = {};
+            $.each(err, function (property, value) {
+                if ($.inArray(property, safariProperties) === -1) {
+                    result[property] = value;
+                }
+            });
+
+            return result;
+        };
+
+        ODataReadOracle.readJsonAcrossServerPages(foodsFeed, function (expectData) {
+            $.each(tests, function (_, test) {
+                createNewCache(options).then(function (cache) {
+                    try {
+                        var expectedResults = {};
+                        if (test.backwards) {
+                            cache.filterBack(test.index, test.count, test.predicate).then(function (results) {
+                                expectedResults = CacheOracle.getExpectedFilterResults(expectData, test.index, test.count, test.predicate, test.backwards);
+                                djstest.assertAreEqualDeep(results, expectedResults, "results for " + testDescription(test));
+                                doneAfterAllTests();
+                            }, filterErrorCallback);
+                        } else {
+                            cache.filterForward(test.index, test.count, test.predicate).then(function (results) {
+                                expectedResults = CacheOracle.getExpectedFilterResults(expectData, test.index, test.count, test.predicate, test.backwards);
+                                djstest.assertAreEqualDeep(results, expectedResults, "results for " + testDescription(test));
+                                doneAfterAllTests();
+                            }, filterErrorCallback);
+                        }
+
+                        if (test.exception) {
+                            djstest.fail("expected exception for " + testDescription(test));
+                            doneAfterAllTests();
+                        }
+                    } catch (err) {
+                        if (test.exception) {
+                            djstest.assertAreEqualDeep(removeSafariExceptionProperties(err), test.exception, "exception for " + testDescription(test));
+                        } else {
+                            djstest.fail("unexpected exception for " + testDescription(test) + ": " + djstest.toString(err));
+                        }
+                        doneAfterAllTests();
+                    }
+                }, thenFailTest);
+            });
+        });
+    });
+
+    djstest.addTest(function createDataCacheTest() {
+        var cache;
+
+        // Verify the defaults.
+        cache = datajs.createDataCache({ name: "name", source: "src" });
+
+        djstest.assertAreEqual(cache.onidle, undefined, "onidle is undefined");
+
+        // Verify specific values.
+        cache = datajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: 2, prefetchSize: 3, idle: 123 });
+
+        djstest.assertAreEqual(cache.onidle, 123, "onidle is as specified");
+
+        // Verify 0 pageSize 
+        djstest.expectException(function () {
+            datajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: 0, prefetchSize: 3, idle: 123 });
+        }, "zero pageSize");
+
+        // Verify negative pageSize
+        djstest.expectException(function () {
+            datajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: -2, prefetchSize: 3, idle: 123 });
+        }, "negative pageSize");
+
+        // Verify NaN pageSize
+        djstest.expectException(function () {
+            cache = datajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: "2", prefetchSize: 3, idle: 123 });
+        }, "NaN pageSize");
+
+        // Verify NaN cacheSize
+        djstest.expectException(function () {
+            cache = datajs.createDataCache({ name: "name", source: "src", cacheSize: "1", pageSize: 2, prefetchSize: 3, idle: 123 });
+        }, "NaN cacheSize");
+
+        // Verify NaN prefetchSize
+        djstest.expectException(function () {
+            cache = datajs.createDataCache({ name: "name", source: "src", cacheSize: 1, pageSize: 2, prefetchSize: "3", idle: 123 });
+        }, "NaN prefetchSize");
+
+        // Verify undefined name 
+        djstest.expectException(function () {
+            datajs.createDataCache({ source: "src", cacheSize: 1, pageSize: 1, prefetchSize: 3, idle: 123 });
+        }, "undefined name");
+
+        // Verify null name 
+        djstest.expectException(function () {
+            datajs.createDataCache({ name: null, source: "src", cacheSize: 1, pageSize: 1, prefetchSize: 3, idle: 123 });
+        }, "null name");
+
+        // Verify undefined source 
+        djstest.expectException(function () {
+            datajs.createDataCache({ name: "name", cacheSize: 1, pageSize: 1, prefetchSize: 3, idle: 123 });
+        }, "undefined source");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function createDataCacheWithSourceTest() {
+        var cacheSource = {
+            count: function (success) {
+                djstest.pass("cacheSource.count was called");
+                success(0);
+            },
+
+            read: function (index, count, success, error) {
+                djstest.assertAreEqual(index, 0, "index is the expected one");
+                djstest.assertAreEqual(count, 10, "test is the expected one");
+                djstest.assert(success, "success is defined");
+                djstest.assert(error, "error is defined");
+                djstest.pass("cacheSource.read was called");
+
+                setTimeout(function () {
+                    success([]);
+                }, 0);
+            }
+        };
+
+        var cache = datajs.createDataCache({ name: "name", source: cacheSource, mechanism: "memory", pageSize: 10 });
+        cache.count().then(function () {
+            cache.readRange(0, 5).then(function () {
+                djstest.done();
+            }, thenFailTest);
+        }, thenFailTest);
+    });
+
+    djstest.addTest(function cacheInitializationFailTest() {
+        // Tests various failure modes for cache initialization.
+        var failures = ["read-settings", "write-settings", "v2"];
+        var failureIndex = 0;
+
+        var originalStore = datajs.createStore;
+        var restoreStore = function () {
+            datajs.createStore = originalStore;
+        };
+
+        var storeError = { message: "cacheInitializationFailTest error" };
+        datajs.createStore = function (name, mechanism) {
+            return {
+                addOrUpdate: function (key, value, successCallback, errorCallback) {
+                    if (failures[failureIndex] === "write-settings") {
+                        window.setTimeout(function () { errorCallback(storeError); }, 2);
+                    } else {
+                        djstest.fail("Error unaccounted for in addOrUpdate for " + failures[failureIndex]);
+                        window.setTimeout(function () { errorCallback(storeError); }, 2);
+                    }
+                },
+                read: function (key, successCallback, errorCallback) {
+                    if (failures[failureIndex] === "read-settings") {
+                        window.setTimeout(function () { errorCallback(storeError); }, 2);
+                    } else if (failures[failureIndex] === "v2") {
+                        window.setTimeout(function () {
+                            successCallback("K", { version: "2.0" });
+                        }, 2);
+                    } else if (failures[failureIndex] === "write-settings") {
+                        window.setTimeout(function () { successCallback(null, null); }, 2);
+                    } else {
+                        djstest.fail("Error unaccounted for read in " + failures[failureIndex]);
+                        window.setTimeout(function () { errorCallback(storeError); }, 2);
+                    }
+                }
+            };
+        };
+
+        var nextFailure = function () {
+            djstest.log("Failure mode: " + failures[failureIndex]);
+            var cache = datajs.createDataCache({ name: "name", source: "foo", mechanism: "memory", pageSize: 10 });
+            try {
+                // The first readRange should succeed, because the data cache isn't really initialized at this time.
+                cache.readRange(1, 2).then(djstest.failAndDoneCallback("No function should succeed"), function (err) {
+                    djstest.expectException(function () {
+                        cache.readRange(1, 2);
+                    }, "readRange after store is invalidated");
+
+                    djstest.expectException(function () {
+                        cache.count();
+                    }, "count after store is invalidated");
+
+                    djstest.expectException(function () {
+                        cache.clear();
+                    }, "clear after store is invalidated");
+
+                    djstest.expectException(function () {
+                        cache.filterForward(1, 2);
+                    }, "filterForward after store is invalidated");
+
+                    djstest.expectException(function () {
+                        cache.filterBack(1, 2);
+                    }, "filterBack after store is invalidated");
+
+                    djstest.expectException(function () {
+                        cache.toObservable();
+                    }, "toObservable after store is invalidated");
+
+                    failureIndex++;
+                    if (failureIndex === failures.length) {
+                        restoreStore();
+                        djstest.done();
+                    } else {
+                        nextFailure();
+                    }
+                });
+            } catch (outerError) {
+                djstest.fail("Unexpected failure for first .readRange: " + window.JSON.stringify(outerError));
+                restoreStore();
+                djstest.done();
+            }
+        };
+
+        nextFailure();
+    });
+
+    djstest.addTest(function createDataCacheWithSourceCallsErrorTest() {
+        var cacheSource = {
+            count: function () {
+                djstest.fail("cacheSource.count was called");
+            },
+
+            read: function (index, count, success, error) {
+                setTimeout(function () {
+                    error({ message: "source error" });
+                }, 0);
+            }
+        };
+
+        var cache = datajs.createDataCache({ name: "name", source: cacheSource, mechanism: "memory", pageSize: 10 });
+        cache.readRange(0, 5).then(function () {
+            djstest.fail("unexpected call to then success");
+            djstest.done();
+        }, function (err) {
+            djstest.assertAreEqual(err.message, "source error", "got the expected error");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function toObservableMissingTest() {
+        createNewCache({ name: "cache", source: "http://temp.org" }).then(function (cache) {
+            var hiddenRx = window.Rx;
+            try {
+                window.Rx = null;
+                var error = null;
+                try {
+                    cache.ToObservable();
+                } catch (err) {
+                    error = err;
+                }
+
+                djstest.assert(error !== null, "error !== null");
+            } finally {
+                window.Rx = hiddenRx;
+            }
+
+            djstest.assert(error !== null, "error !== null");
+            djstest.destroyCacheAndDone(cache);
+        });
+    });
+
+    djstest.addTest(function toObservableSinglePageTest() {
+        createNewCache({ name: "cache", source: foodsFeed }).then(function (cache) {
+            var lastId = -1;
+            cache.ToObservable().Subscribe(function (item) {
+                djstest.assert(lastId < item.FoodID, "lastId < item.FoodID");
+                lastId = item.FoodID;
+            }, thenFailTest, function () {
+                djstest.assert(lastId !== -1, "lastId !== -1");
+                djstest.done();
+            });
+        });
+    });
+
+    djstest.addTest(function toObservableCaseSinglePageTest() {
+        createNewCache({ name: "cache", source: foodsFeed }).then(function (cache) {
+            var lastId = -1;
+            cache.toObservable().Subscribe(function (item) {
+                djstest.assert(lastId < item.FoodID, "lastId < item.FoodID");
+                lastId = item.FoodID;
+            }, thenFailTest, function () {
+                djstest.assert(lastId !== -1, "lastId !== -1");
+                djstest.done();
+            });
+        });
+    });
+
+    djstest.addTest(function toObservableMultiplePageTest() {
+        createNewCache({ name: "cache", source: foodsFeed, pageSize: 2 }).then(function (cache) {
+            var lastId = -1;
+            var callCount = 0;
+            cache.toObservable().Subscribe(function (item) {
+                djstest.assert(lastId < item.FoodID, "lastId < item.FoodID");
+                lastId = item.FoodID;
+                callCount += 1;
+            }, thenFailTest, function () {
+                djstest.assert(lastId !== -1, "lastId !== -1");
+                djstest.assert(callCount > 1, "callCount > 1");
+                djstest.done();
+            });
+        });
+    });
+
+    djstest.addTest(function toObservableEarlyDisposeTest() {
+        createNewCache({ name: "cache", source: foodsFeed, pageSize: 2 }).then(function (cache) {
+            var lastId = -1;
+            var callCount = 0;
+            var complete = false;
+            var observer = cache.toObservable().Subscribe(function (item) {
+                djstest.assert(complete === false, "complete === false");
+                djstest.assert(lastId < item.FoodID, "lastId < item.FoodID");
+                lastId = item.FoodID;
+                callCount += 1;
+                complete = true;
+                observer.Dispose();
+                djstest.done();
+            }, thenFailTest);
+        });
+    });
+
+    djstest.addTest(function toObservableFailureTest() {
+        createNewCache({ name: "cache", source: foodsFeed, pageSize: 2 }).then(function (cache) {
+            var lastId = -1;
+            var complete = false;
+            window.MockHttpClient.clear().addResponse("*", { statusCode: 500, body: "server error" });
+            window.MockHttpClient.async = true;
+            var savedClient = OData.defaultHttpClient;
+            OData.defaultHttpClient = window.MockHttpClient;
+            cache.toObservable().Subscribe(function (item) {
+                OData.defaultHttpClient = savedClient;
+                djstest.fail("Unexpected call to OnNext");
+            }, function (err) {
+                djstest.assert(complete === false, "complete === false");
+                djstest.assert(err, "err defined");
+                OData.defaultHttpClient = savedClient;
+                complete = true;
+                djstest.done();
+            }, function (complete) {
+                djstest.fail("Unexpected call to complete. Error handler should be called.");
+                OData.defaultHttpClient = savedClient;
+                complete = true;
+                djstest.done();
+            });
+        });
+    });
+
+    // DATAJS INTERNAL START
+
+    djstest.addTest(function createDeferredTest() {
+        // Verify basic use of deferred object.
+        var deferred = datajs.createDeferred();
+        deferred.then(function (val1, val2) {
+            djstest.assertAreEqual(val1, 1, "val1 is as specified");
+            djstest.assertAreEqual(val2, 2, "val2 is as specified");
+            djstest.done();
+        });
+        deferred.resolve(1, 2);
+    });
+
+    djstest.addTest(function deferredThenTest() {
+        // Verify then registration and chaining.
+        var deferred = datajs.createDeferred();
+        deferred.then(function (val1, val2) {
+            djstest.assertAreEqual(val1, 1, "val1 is as specified");
+            djstest.assertAreEqual(val2, 2, "val2 is as specified");
+            return "foo";
+        }).then(function (foo) {
+            // See Compatibility Note B in DjsDeferred remarks.
+            djstest.assert(foo !== "foo", "argument for chained 'then' is *not* result of previous call");
+            djstest.assert(foo === 1, "argument for chained 'then' is same as for previous call");
+
+            var other = datajs.createDeferred();
+            other.then(null, function (err, msg) {
+                djstest.assertAreEqual("error", err, "err is as specified");
+                djstest.assertAreEqual("message", msg, "msg is as specified");
+
+            }).then(null, function (err, msg) {
+                djstest.log("chained errors are called");
+
+                djstest.assertAreEqual("error", err, "err is as specified");
+                djstest.assertAreEqual("message", msg, "msg is as specified");
+
+                var multiple = datajs.createDeferred();
+                var count = 0;
+
+                // See Compatibility Note A in DjsDeferred remarks.
+                multiple.then(function () {
+                    djstest.assertAreEqual(count, 0, "first base registration fires as #0");
+                    count++;
+                }).then(function () {
+                    djstest.assertAreEqual(count, 1, "first chained registration fires as #1");
+                    count++;
+                });
+
+                multiple.then(function () {
+                    djstest.assertAreEqual(count, 2, "second base registration fires as #2");
+                    count++;
+                }).then(function () {
+                    djstest.assertAreEqual(count, 3, "second chained registration fires as #3");
+                    djstest.done();
+                });
+
+                multiple.resolve();
+            });
+            other.reject("error", "message");
+        });
+
+        deferred.resolve(1, 2);
+    });
+
+    djstest.addTest(function deferredResolveTest() {
+        // Resolve with no arguments.
+        var deferred = datajs.createDeferred();
+        deferred.then(function (arg) {
+            djstest.assertAreEqual(arg, undefined, "resolve with no args shows up as undefined");
+
+            // Resolve with no callbacks.
+            var other = datajs.createDeferred();
+            other.resolve();
+            djstest.done();
+        });
+
+        deferred.resolve();
+    });
+
+    djstest.addTest(function deferredRejectTest() {
+        // Resolve with no arguments.   
+        var deferred = datajs.createDeferred();
+        deferred.then(null, function (arg) {
+            djstest.assertAreEqual(arg, undefined, "reject with no args shows up as undefined");
+
+            // Resolve with no callbacks.
+            var other = datajs.createDeferred();
+            other.reject();
+            djstest.done();
+        });
+
+        deferred.reject();
+    });
+
+    djstest.addTest(function estimateSizeTest() {
+        var tests = [
+            { i: null, e: 8 },
+            { i: undefined, e: 8 },
+            { i: 0, e: 8 },
+            { i: "abc", e: 6 },
+            { i: [1, 2, 3], e: 30 },
+            { i: { a1: null, a2: undefined, a3: 5, a4: "ab", a5: { b1: 5, b2: 6} }, e: 72 },
+            { i: {}, e: 0 }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var test = tests[i];
+            djstest.assertAreEqual(datajs.estimateSize(test.i), test.e);
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function cacheOptionsTunnelTest() {
+        var mockClient = window.MockHttpClient;
+        var doneCalled = false;
+
+        mockClient.clear().setAsync(true).addRequestVerifier("*", function (theRequest) {
+            if (!doneCalled) {
+                doneCalled = true;
+                djstest.assertAreEqual(theRequest.user, "the-user", "theRequest.user");
+                djstest.assertAreEqual(theRequest.password, "the-password", "theRequest.password");
+                djstest.assertAreEqual(theRequest.enableJsonpCallback, false, "theRequest.enableJsonpCallback");
+                djstest.assertAreEqual(theRequest.callbackParameterName, "p", "callbackParameterName");
+                djstest.done();
+            }
+        });
+
+        var cache = datajs.createDataCache({
+            name: "cacheOptionsTunnel",
+            source: "http://foo-bar/",
+            user: "the-user",
+            password: "the-password",
+            enableJsonpCallback: false,
+            callbackParameterName: "p",
+            httpClient: mockClient
+        });
+
+        cache.readRange(0, 10).then(function (arr) {
+            djstest.fail("unexpected callback into readRange in test cacheOptionsTunnelTest");
+            if (!doneCalled) {
+                doneCalled = true;
+                djstest.done();
+            }
+        });
+    });
+
+    djstest.addTest(function dataCacheHandlesFullStoreTest() {
+
+        var TestStore = function (name) {
+            var that = new window.datajs.MemoryStore(name);
+            that.addOrUpdate = function (key, value, success, error) {
+                if (key === "__settings") {
+                    window.setTimeout(function () {
+                        success(key, value);
+                    }, 0);
+                } else {
+                    window.setTimeout(function () {
+                        error({ name: "QUOTA_EXCEEDED_ERR" });
+                    }, 0);
+                }
+            };
+            return that;
+        };
+
+        TestStore.create = function (name) {
+            return new TestStore(name);
+        };
+
+        TestStore.isSupported = function () {
+            return true;
+        };
+
+        var cacheSource = {
+            identifier: "testSource",
+            count: function (success) {
+                djstest.fail("cacheSource.count was called");
+                success(5);
+            },
+            read: function (index, count, success, error) {
+                djstest.assertAreEqual(index, 0, "index is the expected one");
+                djstest.assertAreEqual(count, 5, "test is the expected one");
+
+                setTimeout(function () {
+                    success({ value: [1, 2, 3, 4, 5] });
+                }, 0);
+            }
+        };
+
+        var originalCreateStore = window.datajs.createStore;
+
+        window.datajs.createStore = function (name, mechanism) {
+            return TestStore(name);
+        };
+
+        try {
+            var cache = datajs.createDataCache({
+                name: "cache",
+                pageSize: 5,
+                prefetchSize: 0,
+                source: cacheSource,
+                mechanism: "teststore"
+            });
+        } finally {
+            window.datajs.createStore = originalCreateStore;
+        }
+
+        cache.readRange(0, 5).then(function (data) {
+            djstest.assertAreEqual(data.value.length, 5, "5 items were read.");
+            cache.readRange(0, 5).then(function (data) {
+                djstest.assertAreEqual(data.value.length, 5, "5 items were read.");
+                djstest.done();
+            }, thenFailTest);
+        }, thenFailTest);
+    });
+
+    // DATAJS INTERNAL END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/CacheOracle.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/CacheOracle.js b/JSLib/tests/common/CacheOracle.js
new file mode 100644
index 0000000..2130f82
--- /dev/null
+++ b/JSLib/tests/common/CacheOracle.js
@@ -0,0 +1,228 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// CacheOracle.js
+// This object verifies the operation of the cache.
+// Internally it maintains a simple model of the cache implemented using a lookup array of the expected cached pages.
+
+(function (window, undefined) {
+
+    var CacheOracle = function (baseUri, pageSize, total, cacheSize) {
+        /// <summary>Creates a new CacheOracle</summary>
+        /// <param name="baseUri" type="String">The base URI of the collection</param>
+        /// <param name="pageSize" type="Integer">The page size used in the cache</param>
+        /// <param name="total" type="Integer">The total number of items in the collection</param>
+        /// <param name="cacheSize" type="Integer">Cache size in bytes</param>
+        this.baseUri = baseUri;
+        this.pageSize = pageSize;
+        this.total = total;
+        this.cacheSize = (cacheSize !== undefined) ? cacheSize : 1024 * 1024;
+        this.actualSize = 0;
+        this.actualCount = 0;
+        this.cachedPages = [];
+        this.exactPageCount = (total % pageSize === 0);
+        this.maxPage = Math.floor(total / pageSize);
+        this.overflowed = this.cacheSize === 0;
+    };
+
+    CacheOracle.mechanisms = {
+        memory: "memory",
+        indexeddb: "indexeddb",
+        dom: "dom",
+        best: "best"
+    };
+
+    CacheOracle.isMechanismAvailable = function (mechanism) {
+        /// <summary>Determines if the specified local storage mechanism is available</summary>
+        /// <param name="mechanism">The name of the mechanism</param>
+        /// <returns>Whether the mechanism is available</returns>
+        switch (mechanism) {
+            case CacheOracle.mechanisms.indexeddb:
+                if (window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.indexedDB) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+                break;
+            case CacheOracle.mechanisms.dom:
+                if (window.localStorage) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+                break;
+            case CacheOracle.mechanisms.memory:
+            case CacheOracle.mechanisms.best:
+            case undefined:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    CacheOracle.prototype.clear = function () {
+        /// <summary>Clears the cache in the oracle</summary>
+        this.cachedPages = [];
+        this.actualSize = 0;
+        this.actualCount = 0;
+        this.overflowed = this.cacheSize === 0;
+    }
+
+    CacheOracle.prototype.verifyRequests = function (requests, responses, index, count, description, backwards, isPrefetch) {
+        /// <summary>Verifies the HTTP requests for a single data request, and updates the oracle with cached pages</summary>
+        /// <param name="requests" type="Array">The sequence of request objects (from OData.defaultHttpClient)</param>
+        /// <param name="responses" type="Array">The sequence of response objects (from OData.defaultHttpClient)</param>
+        /// <param name="index" type="Integer">The starting index of the read</param>
+        /// <param name="count" type="Integer">The count of items in the read</param>
+        /// <param name="description" type="String">The description of the requests being verified</param>
+        /// <param name="backwards" type="Boolean">Whether or not filterBack is being verified</param>
+        /// <param name="isPrefetch" type="Boolean">Whether the requests being verified come from the prefetcher</param>
+        var that = this;
+
+        index = (index < 0 ? 0 : index);
+        var pageIndex = function (index) {
+            /// <summary>Returns the page index that the given item index belongs to</summary>
+            /// <param name="index" type="Integer">The item index</param>
+            /// <returns>The page index</returns>
+            return Math.floor(index / that.pageSize);
+        };
+
+        var estimateSize = function (obj) {
+            /// <summary>Estimates the size of an object in bytes.</summary>
+            /// <param name="obj" type="Object">Object to determine the size of.</param>
+            /// <returns type="Number">Estimated size of the object in bytes.</returns>
+
+            var size = 0;
+            var type = typeof obj;
+
+            if (type === "object" && obj) {
+                for (var name in obj) {
+                    size += name.length * 2 + estimateSize(obj[name]);
+                }
+            } else if (type === "string") {
+                size = obj.length * 2;
+            } else {
+                size = 8;
+            }
+            return size;
+        };
+
+        var expectedUris = [];
+        var responseIndex = 0;
+        if (count >= 0) {
+            var minPage = pageIndex(index);
+            var maxPage = Math.min(pageIndex(index + count - 1), pageIndex(this.total));
+
+            // In the case that the index is outside the range of the collection the minPage will be greater than the maxPage  
+            maxPage = Math.max(minPage, maxPage);
+
+            if (!(isPrefetch && !this.exactPageCount && minPage > this.maxPage)) {
+                for (var page = minPage; page <= maxPage && this.actualCount <= this.total && !(isPrefetch && this.overflowed); page++) {
+                    if (!this.cachedPages[page]) {
+
+                        expectedUris.push(that.baseUri + "?$skip=" + page * this.pageSize + "&$top=" + (this.pageSize));
+
+                        var actualPageSize = 0;
+                        var actualPageCount = 0;
+                        if (responses[responseIndex] && responses[responseIndex].data) {
+                            actualPageSize += estimateSize(responses[responseIndex].data);
+                            actualPageCount += responses[responseIndex].data.value.length;
+                            // Handle server paging skipToken requests
+                            while (responses[responseIndex].data["@odata.nextLink"]) {
+                                var nextLink = responses[responseIndex].data["@odata.nextLink"];
+                                if (nextLink) {
+                                    var index = that.baseUri.indexOf(".svc/", 0);
+                                    if (index != -1) {
+                                        nextLink = that.baseUri.substring(0, index + 5) + nextLink;
+                                    }
+                                }
+
+                                expectedUris.push(nextLink);
+                                responseIndex++;
+                                actualPageSize += estimateSize(responses[responseIndex].data);
+                                actualPageCount += responses[responseIndex].data.value.length;
+                            }
+
+                            actualPageSize += 24; // 24 byte overhead for the pages (i)ndex, and (c)ount fields
+                        }
+
+                        responseIndex++;
+
+                        this.overflowed = this.cacheSize >= 0 && this.actualSize + actualPageSize > this.cacheSize;
+                        if (!this.overflowed) {
+                            this.cachedPages[page] = true;
+                            this.actualSize += actualPageSize;
+                            this.actualCount += actualPageCount;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (backwards) {
+            expectedUris.reverse();
+        }
+
+        var actualUris = $.map(requests, function (r) { return r.requestUri; });
+        djstest.assertAreEqualDeep(actualUris, expectedUris, description);
+    };
+
+    CacheOracle.getExpectedFilterResults = function (data, filterIndex, filterCount, predicate, backwards) {
+        /// <summary>Verifies the cache filter returns the correct data</summary>
+        /// <param name="collection" type="Array">Array of items in the collection</param>
+        /// <param name="filterIndex" type="Integer">The index value</param>
+        /// <param name="filterCount" type="Integer">The count value</param>
+        /// <param name="predicate" type="Function">Predicate to be applied in filter, takes an item</param>
+        /// <param name="backwards" type="Boolean">Whether or not filterBackwards is being verified</param>
+        if (!data || !data.value) {
+            return data;
+        }
+
+        var value = [];
+        if (filterCount !== 0) {
+            // Convert [item0, item1, ...] into [{ index: 0, item: item0 }, { index: 1, item: item1 }, ...]
+            var indexedCollection = $.map(data.value, function (item, index) {
+                return { index: index, item: item };
+            });
+
+            var grepPredicate = function (element, index) {
+                return predicate(element.item);
+            };
+
+            var index = filterIndex < 0 ? 0 : filterIndex;
+            var count = filterCount < 0 ? indexedCollection.length : filterCount;
+
+            value = backwards ?
+            // Slice up to 'index', filter, then slice 'count' number of items from the end
+                $.grep(indexedCollection.slice(0, index + 1), grepPredicate).slice(-count) :
+            // Slice from 'index' to the end, filter, then slice 'count' number of items from the beginning
+                $.grep(indexedCollection.slice(index), grepPredicate).slice(0, count);
+        }
+
+        var expectedResults = {};
+        for (var property in data) {
+            if (property == "value") {
+                expectedResults[property] = value;
+            } else {
+                expectedResults[property] = data[property];
+            }
+        }
+
+        return expectedResults;
+    };
+
+    window.CacheOracle = CacheOracle;
+
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/Instrument.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/Instrument.js b/JSLib/tests/common/Instrument.js
new file mode 100644
index 0000000..acbb2b0
--- /dev/null
+++ b/JSLib/tests/common/Instrument.js
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Instrument.js
+// Instrumentation utilities
+
+(function (window, undefined) {
+
+    var warmedUp = false;
+    var getBrowserMemorySize = function (success) {
+        /// <summary>Gets the memory size (in bytes) of the browser process</summary>
+        /// <param name="success" type="Function">The success callback</param>
+        var makeRequest = function (success) {
+            $.get("./common/Instrument.svc/GetBrowserMemorySize", function (data) {
+                success(parseInt(data));
+            }, "text");
+        };
+
+        if (window.CollectGarbage) {
+            window.CollectGarbage();
+        }
+
+        if (!warmedUp) {
+            // Make a dummy request to warm it up
+            makeRequest(function () {
+                warmedUp = true;
+                makeRequest(success);
+            });
+        } else {
+            makeRequest(success);
+        }
+    }
+
+    window.Instrument = {
+        getBrowserMemorySize: getBrowserMemorySize
+    };
+
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/Instrument.svc
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/Instrument.svc b/JSLib/tests/common/Instrument.svc
new file mode 100644
index 0000000..3d14b16
--- /dev/null
+++ b/JSLib/tests/common/Instrument.svc
@@ -0,0 +1,71 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
+    Service="DataJS.Tests.Instrument" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.IO;
+    using System.Linq;
+    using System.Runtime.Serialization;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Syndication;
+    using System.ServiceModel.Web;
+    using System.Text;
+
+    /// <summary>
+    /// Instrumentation utilities
+    /// </summary>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class Instrument
+    {
+        static readonly Dictionary<string, string> userAgents = new Dictionary<string,string>
+        {
+            { "MSIE", "iexplore" },
+            { "Firefox", "firefox" },
+            { "Chrome", "chrome" },
+            { "Safari", "safari" }
+        };
+        
+        /// <summary>
+        /// Gets the memory size used by the browser
+        /// </summary>
+        /// <returns>The memory size used by the browser (in bytes), or zero if browser is not supported</returns>
+        [OperationContract]
+        [WebGet]
+        public Stream GetBrowserMemorySize()
+        {
+            string userAgentString = WebOperationContext.Current.IncomingRequest.UserAgent;
+            string userAgentKey = Instrument.userAgents.Keys.FirstOrDefault(ua => userAgentString.Contains(ua));
+
+            if (userAgentKey != null)
+            {
+                string processName = userAgents[userAgentKey];
+                long totalMemory = Process.GetProcessesByName(processName).Select(p => p.WorkingSet64).Sum();
+                
+                return new MemoryStream(Encoding.UTF8.GetBytes(totalMemory.ToString()));
+            }
+            else
+            {
+                return new MemoryStream(Encoding.UTF8.GetBytes("0"));
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/ObservableHttpClient.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/ObservableHttpClient.js b/JSLib/tests/common/ObservableHttpClient.js
new file mode 100644
index 0000000..2b7fe98
--- /dev/null
+++ b/JSLib/tests/common/ObservableHttpClient.js
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// ObservableHttpClient.js
+// This object extends OData's default httpClient by supporting request and response recording sessions, and firing a custom
+// JQuery event for each request/response.
+//
+// The events fired by this object are:
+//      request: Before a request is made
+//      success: Before the primary success handler is called
+//
+// To bind to an event, JQuery event attachers can be used on the object, e.g.
+//      $(observableHttpClient).bind("request", function (request) { ... });
+//
+// To begin a new recording session, use:
+//      var session = observableHttpClient.newSession();
+//
+// Requests and responses are then recorded in session.requests and session.responses. Session can be ended by session.end().
+// Multiple simultaneous sessions are supported.
+
+(function (window, undefined) {
+
+    var ObservableHttpClient = function (provider) {
+        this.provider = provider ? provider : OData.defaultHttpClient;
+    };
+
+    ObservableHttpClient.prototype.newSession = function () {
+        return new Session(this);
+    };
+
+    ObservableHttpClient.prototype.request = function (request, success, error) {
+        var that = this;
+
+        $(this).triggerHandler("request", request);
+        return this.provider.request(request, function (response) {
+            $(that).triggerHandler("success", response);
+            success(response);
+        }, error);
+    };
+
+
+    var Session = function (client) {
+        var that = this;
+
+        this.client = client;
+        this.clear();
+
+        this.requestHandler = function (event, request) { that.requests.push(request); };
+        $(client).bind("request", this.requestHandler);
+
+        this.successHandler = function (event, response) { that.responses.push(response); };
+        $(client).bind("success", this.successHandler);
+    };
+
+    Session.prototype.clear = function () {
+        this.requests = [];
+        this.responses = [];
+    }
+
+    Session.prototype.end = function () {
+        $(this.client).unbind("request", this.requestHandler);
+        $(this.client).unbind("success", this.successHandler);
+    };
+
+    window.ObservableHttpClient = ObservableHttpClient;
+    window.Session = Session;
+
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/gpo-ie8-tour-disable.reg
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/gpo-ie8-tour-disable.reg b/JSLib/tests/common/gpo-ie8-tour-disable.reg
new file mode 100644
index 0000000..52dfd99
Binary files /dev/null and b/JSLib/tests/common/gpo-ie8-tour-disable.reg differ

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/common/mockHttpClient.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/common/mockHttpClient.js b/JSLib/tests/common/mockHttpClient.js
new file mode 100644
index 0000000..de2a69c
--- /dev/null
+++ b/JSLib/tests/common/mockHttpClient.js
@@ -0,0 +1,107 @@
+//mockHttpClient.js
+//this object allows for associating a uri with a requestVerfier and mock responses that will be sent back to the client of the httpStack.  
+//It can be used to replace OData's httpClient for testing purposes.
+//
+//RequestVerifiers
+//
+//    A request verifier is a function associated to a particular URI that will be executed by the mockHttpClient when it receives a request with the matching URI.
+//    the callback receives as its only parameter the request object passed to the mockHttpClient.
+//
+//    To register a request verifier, simply do 
+//        
+//            MockHttpClient.addRequestVerifier("http://someUri", function(request) {
+//                djstest.assertAreEqual(request.requestUri,"http://someUri");
+//            }
+//
+//Responses
+//    Mock responses can be associated with a particular URI.  When the MockHttpClient receives a request with a URI mapped to a response, then it will, 
+//    depending on the response status code invoke either the success or the error callbacks. 
+//
+//    To register a response,
+//       
+//           MockHttpClient.addResponse("http://someUri", {status: 200, body:"some body"});
+//
+//Exceptions
+//    MockHttpClient will throw an exception if it receives a request to a URI that is not mapped to either a request verifier or a response.
+//
+
+(function (window, undefined) {
+    if (!window.MockHttpClient) {
+        window.MockHttpClient = {};
+    }
+
+    var httpClient = window.MockHttpClient;
+
+    var responses = {};
+    var requestVerifiers = {};
+
+    httpClient.addRequestVerifier = function (uri, verifier) {
+        requestVerifiers[uri] = verifier;
+        return this;
+    };
+
+    httpClient.addResponse = function (uri, response) {
+        responses[uri] = response;
+        return this;
+    };
+
+    httpClient.async = false;
+
+    httpClient.clear = function () {
+        /// <summary>Clears all registered responses and verifiers.</summary>
+        /// <returns>this client.</returns>
+        responses = {};
+        requestVerifiers = {};
+        this.async = false;
+        return this;
+    };
+
+    httpClient.request = function (request, success, error) {
+        var uri = request.requestUri;
+        var verifier = requestVerifiers[uri];
+        var response = responses[uri];
+
+        if (verifier === undefined) {
+            verifier = requestVerifiers["*"];
+        }
+
+        if (response === undefined) {
+            response = responses["*"];
+        }
+
+        if (!verifier && !response) {
+            throw { message: "neither verifier or response defined for uri: " + uri };
+        }
+
+        if (verifier) {
+            verifier(request);
+        }
+
+        if (response) {
+            response.requestUri = uri;
+            if (response.statusCode >= 200 && response.statusCode <= 299) {
+                if (this.async) {
+                    setTimeout(function () {
+                        success(response);
+                    });
+                } else {
+                    success(response);
+                }
+            } else {
+                if (this.async) {
+                    setTimeout(function () {
+                        error({ message: "failed test response", request: request, response: response });
+                    });
+                }
+                else {
+                    error({ message: "failed test response", request: request, response: response });
+                }
+            }
+        }
+    };
+
+    httpClient.setAsync = function (value) {
+        this.async = value;
+        return this;
+    };
+})(this);


[02/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-read-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-read-functional-tests.js b/JSLib/tests/odata-read-functional-tests.js
new file mode 100644
index 0000000..ad2a9ea
--- /dev/null
+++ b/JSLib/tests/odata-read-functional-tests.js
@@ -0,0 +1,583 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;q=0.1";
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    // to do: enable the Atom/XML senario
+    var validServiceDocumentAcceptHeaders = [
+            "*/*",
+    //"application/xml",
+            "application/json",
+            undefined
+          ];
+
+    var validMetadataAcceptHeaders = [
+            "*/*",
+            "application/xml",
+            undefined
+          ];
+
+    var invalidServiceDocumentAcceptHeaders = [
+        "application/atom+xml"
+    ];
+
+    var invalidMetadataAcceptHeaders = [
+            "application/atom+xml",
+            "application/json"
+        ];
+
+    var handlerAcceptStrings = [
+        "*/*",
+    //      "application/atom+xml",
+        "application/json",
+         undefined
+      ];
+
+    var httpStatusCode = {
+        notFound: 404,
+        badRequest: 400,
+        unsupportedMediaType: 415
+    };
+
+    var service = "./endpoints/FoodStoreDataServiceV4.svc/";
+    var epmService = "./endpoints/EpmDataService.svc/";
+    var feed = service + "Foods";
+    var categoriesFeed = service + "Categories";
+
+    var expectedErrorMessage = "HTTP request failed";
+
+    module("Functional", {
+        setup: function () {
+            djstest.wait(function (done) {
+                $.post(service + "ResetData", done);
+            });
+            OData.jsonHandler.recognizeDates = false;
+        }
+    });
+
+    for (var i = 0; i < handlerAcceptStrings.length; i++) {
+
+        djstest.addTest(function readFullFeedTest(handlerAccept) {
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: feed, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readFeed(feed,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                );
+        }, "Testing valid read of full feed collection with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readMaxAndNullValueEntryTest(handlerAccept) {
+            var endPoint = feed + "(0)";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endPoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                );
+        }, "Testing valid read of entry with max numbers, complex types, and null and empty strings " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readMinAndZeroValueEntryTest(handlerAccept) {
+            var endPoint = feed + "(1)";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: "./endpoints/FoodStoreDataServiceV4.svc/Foods(1)", headers: { Accept: handlerAccept} },
+                function (data, response) {
+                    window.ODataReadOracle.readEntry(endPoint,
+                        function (expectedData) {
+                            djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                            djstest.done();
+                        }, handlerAccept
+                    );
+                },
+                unexpectedErrorHandler);
+        }, "Testing valid read of minimum and zero values " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readNullNestedComplexTypeEntryTest(handlerAccept) {
+            var endPoint = feed + "(2)";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endPoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                 );
+        }, "Testing valid read of null nested complex type and navigation property " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readNullComplexTypeEntryTest(handlerAccept) {
+            var endPoint = feed + "(3)";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endPoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                 );
+        }, "Testing valid read of null top level complex type" + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readNullPropertiesDerivedEntryTest(handlerAccept) {
+            djstest.assertsExpected(1);
+            var endPoint = feed + "(4)";
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endPoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                );
+        }, "Testing valid read of derived type null properties with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readNextComplexTypeDerivedEntryTest(handlerAccept) {
+
+            djstest.assertsExpected(1);
+            var endPoint = feed + "(5)";
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endPoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                 );
+        }, "Testing valid read of derived type with full nested complex type properties with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readEntryWithInlineFeedTest(handlerAccept) {
+            var endpoint = categoriesFeed + "(0)?$expand=Foods";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data.value, expectedData.value, "Verify inline feed");
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify entry");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                );
+        }, "Testing read of entry with inline feed with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readFeedWithEmptyInlineFeedTest(handlerAccept) {
+            var endpoint = categoriesFeed + "?$filter=Name eq 'Empty Category'&$expand=Foods";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readFeed(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data.value, expectedData.value, "Verify inline feed");
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify feed");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+               );
+        }, "Testing read of entry with empty inline feed with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readEntryWithInlineEntryTest(handlerAccept) {
+            var endpoint = feed + "(0)?$expand=Category";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data.Category, expectedData.Category, "Verify inline entry");
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify entry");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+               );
+        }, "Testing read of entry with inline entry with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readFeedWithNullInlineEntryTest(handlerAccept) {
+            var endpoint = feed + "?$expand=Category&$filter=Category eq null";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readFeed(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data.value, expectedData.value, "Verify inline data");
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify feed");
+                                djstest.done();
+                            }, handlerAccept);
+                    },
+                    unexpectedErrorHandler
+               );
+        }, "Testing read of feed with null inline entry with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readFeedWithInlineCountTest(handlerAccept) {
+            var endpoint = feed + "?$count=true";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readFeed(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqual(data["@odata.count"], expectedData["@odata.count"], "Verify count in response data");
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify feed");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    },
+                    unexpectedErrorHandler
+                );
+        }, "Testing read of collection with inline count with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function selectSinglePropertyOnEntryTest(handlerAccept) {
+            var endpoint = feed + "(0)?$select=Name";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                function (data, response) {
+                    window.ODataReadOracle.readEntry(endpoint,
+                        function (expectedData) {
+                            djstest.assertAreEqualDeep(data, expectedData, "Verify select result");
+                            djstest.done();
+                        }, handlerAccept
+                    );
+                },
+                unexpectedErrorHandler);
+        }, "Select single property of entry " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function selectComplexTypeOnFeedTest(handlerAccept) {
+            var endpoint = feed + "?$select=Packaging";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                function (data, response) {
+                    window.ODataReadOracle.readFeed(endpoint,
+                        function (expectedData) {
+                            djstest.assertAreEqualDeep(data, expectedData, "Verify select result");
+                            djstest.done();
+                        }, handlerAccept
+                    );
+                },
+                unexpectedErrorHandler);
+        }, "Select single complex type property of feed " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function selectMultiplePropertiesOnEntryTest(handlerAccept) {
+            var endpoint = feed + "(3)?$select=Packaging,ExpirationDate,IsAvailable";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                function (data, response) {
+                    window.ODataReadOracle.readEntry(endpoint,
+                        function (expectedData) {
+                            djstest.assertAreEqualDeep(data, expectedData, "Verify select result");
+                            djstest.done();
+                        }, handlerAccept
+                    );
+                },
+                unexpectedErrorHandler);
+        }, "Select multiple primitive properties of feed " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readPagedCategoriesCollectionTest(handlerAccept) {
+            var endpoint = categoriesFeed;
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readFeed(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify response data");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    }, unexpectedErrorHandler);
+        }, "Testing read of paged collection with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readPagedCollectionWithInlineCountTest(handlerAccept) {
+            var endpoint = categoriesFeed + "?$count=true";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readFeed(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqual(data["@odata.context"], expectedData["@odata.context"], "Verify count in response data");
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify feed");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    }, unexpectedErrorHandler);
+        }, "Testing read of paged collection with inline count with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readEntryWithNamedStreams(handlerAccept) {
+            var endpoint = feed + "(1)?$expand=Category";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify entry");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    }, unexpectedErrorHandler);
+        }, "Testing read of entry with named streams " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readEntryWithCollectionProperties(handlerAccept) {
+            var endpoint = feed + "(0)";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        window.ODataReadOracle.readEntry(endpoint,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Verify entry");
+                                djstest.done();
+                            }, handlerAccept
+                        );
+                    }, unexpectedErrorHandler);
+        }, "Testing read of entry with collection properties " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function invalidEntryReadTest(handlerAccept) {
+            var endPoint = feed + "(16)";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        djstest.assert(false, "We should not get here because data is not valid.");
+                        djstest.done()
+                    },
+                    function (err) {
+                        djstest.assertAreEqual(err.message, expectedErrorMessage, "Error message");
+                        djstest.assertAreEqual(err.response.statusCode, httpStatusCode.notFound, "Response status code");
+                        djstest.done();
+                    });
+        }, "Testing invalid entry read with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function invalidFeedReadTest(handlerAccept) {
+            var endPoint = feed + "Invalid";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                    function (data, response) {
+                        djstest.assert(false, "We should not get here because data is not valid.");
+                        djstest.done();
+                    },
+                    function (err) {
+                        djstest.assertAreEqual(err.message, expectedErrorMessage, "Error message");
+                        djstest.assertAreEqual(err.response.statusCode, httpStatusCode.notFound, "Response status code");
+                        djstest.done();
+                    });
+        }, "Testing invalid feed read with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function standardErrorReadTest(handlerAccept) {
+            var endPoint = feed + "?$foo=bar";
+            djstest.assertsExpected(2);
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} },
+                                    function (data, response) {
+                                        djstest.assert(false, "We should not get here because data is not valid.");
+                                        djstest.done()
+                                    },
+                                    function (err) {
+                                        djstest.assertAreEqual(err.message, expectedErrorMessage, "Error message");
+                                        djstest.assertAreEqual(err.response.statusCode, httpStatusCode.badRequest, "Response status code");
+                                        djstest.done();
+                                    });
+        }, "Testing standard error read with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function inStreamErrorReadTest(handlerAccept) {
+            var endPoint = "./endpoints/ErrorDataService.svc/Entities";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endPoint, headers: { Accept: handlerAccept} }, function (data, response) {
+                djstest.assert(false, "Unexpected call to success handler with response: " + djstest.toString(response));
+                djstest.done()
+            }, function (err) {
+                djstest.assert(err.response.body.indexOf("An error occurred while processing this request") > -1, "Error handler was called with the correct response body");
+                djstest.done();
+            });
+        }, "Testing in-stream error read with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        var user = "djsUser";
+        var password = "djsPassword";
+
+        djstest.addTest(function readFullFeedBasicAuthTest(handlerAccept) {
+            var endpoint = "./endpoints/BasicAuthDataService.svc/Customers";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept }, user: user, password: password }, function (data, response) {
+                window.ODataReadOracle.readFeed({ url: endpoint, user: user, password: password }, function (expectedData) {
+                    djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                    djstest.done();
+                }, handlerAccept);
+            }, unexpectedErrorHandler);
+        }, "Testing valid read of full feed collection on basic auth with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+
+        djstest.addTest(function readEntryBasicAuthTest(handlerAccept) {
+            var endpoint = "./endpoints/BasicAuthDataService.svc/Customers(1)";
+            djstest.assertsExpected(1);
+            OData.read({ requestUri: endpoint, headers: { Accept: handlerAccept }, user: user, password: password }, function (data, response) {
+                window.ODataReadOracle.readEntry({ url: endpoint, user: user, password: password }, function (expectedData) {
+                    djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                    djstest.done();
+                }, handlerAccept);
+            }, unexpectedErrorHandler);
+        }, "Testing valid read of entry on basic auth with " + handlerAcceptStrings[i], handlerAcceptStrings[i]);
+    }
+
+    var services = [
+            service,
+            epmService
+        ];
+
+    $.each(services, function (_, serviceName) {
+        $.each(validServiceDocumentAcceptHeaders, function (_, validServiceDocumentAcceptHeader) {
+            var parameters = { handlerAccept: validServiceDocumentAcceptHeader, serviceName: serviceName };
+
+            djstest.addTest(function validReadServiceDocumentTest(params) {
+                djstest.assertsExpected(1);
+                OData.read({ requestUri: params.serviceName, headers: { Accept: params.handlerAccept} },
+                        function (data, response) {
+                            window.ODataReadOracle.readServiceDocument(serviceName,
+                                function (expectedData) {
+                                    djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                    djstest.done();
+                                }, params.handlerAccept
+                            );
+                        },
+                        unexpectedErrorHandler
+                    );
+            }, "Testing valid read of service document " + parameters.handlerAccept + " on service " + parameters.serviceName, parameters);
+        });
+
+        $.each(invalidServiceDocumentAcceptHeaders, function (_, invalidServiceDocumentAcceptHeader) {
+            var parameters = { handlerAccept: invalidServiceDocumentAcceptHeader, serviceName: serviceName };
+
+            djstest.addTest(function invalidReadServiceDocumentTest(params) {
+                djstest.assertsExpected(2);
+                OData.read({ requestUri: params.serviceName, headers: { Accept: params.handlerAccept} },
+                        function success(data, response) {
+                            djstest.fail("Reading service document should produce error with " + params.handlerAccept);
+                            djstest.done();
+                        },
+                        function (err) {
+                            djstest.assertAreEqual(err.message, expectedErrorMessage, "Error message");
+                            djstest.assertAreEqual(err.response.statusCode, httpStatusCode.unsupportedMediaType, "Response status code");
+                            djstest.done();
+                        }
+                    );
+            }, "Testing read of service document with invalid MIME type " + parameters.invalidServiceDocumentAcceptHeader + " on service " + serviceName, parameters);
+        });
+
+        //to do:
+        $.each(validMetadataAcceptHeaders, function (_, validMetadataAcceptHeader) {
+            var parameters = { handlerAccept: validMetadataAcceptHeader, serviceName: serviceName };
+
+            djstest.addTest(function validReadMetadataTest(params) {
+                djstest.assertsExpected(1);
+                var endPoint = params.serviceName + "$metadata";
+                OData.read({ requestUri: endPoint, headers: { Accept: params.handlerAccept} },
+                        function (data, response) {
+                            window.ODataReadOracle.readMetadata(endPoint,
+                                function (expectedData) {
+                                    djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                    djstest.done();
+                                }
+                            );
+                        },
+                        unexpectedErrorHandler,
+                        OData.metadataHandler
+                    );
+            }, "Testing valid read metadata " + parameters.handlerAccept + " on service " + parameters.serviceName, parameters);
+        });
+
+        $.each(invalidMetadataAcceptHeaders, function (_, invalidMetadataAcceptHeader) {
+            var parameters = { handlerAccept: invalidMetadataAcceptHeader, serviceName: serviceName };
+            djstest.addTest(function invlaidReadMetadataTest(params) {
+                djstest.assertsExpected(2);
+                var endPoint = params.serviceName + "$metadata";
+                OData.read({ requestUri: endPoint, headers: { Accept: params.handlerAccept} },
+                        function success(data, response) {
+                            djstest.fail("Reading metadata should produce error with " + params.handlerAccept);
+                            djstest.done();
+                        },
+                        function (err) {
+                            djstest.assertAreEqual(err.message, expectedErrorMessage, "Error message");
+                            djstest.assertAreEqual(err.response.statusCode, httpStatusCode.unsupportedMediaType, "Response status code");
+                            djstest.done();
+                        },
+                        OData.metadataHandler
+                    );
+            }, "Testing read metadata with invalid MIME type " + parameters.handlerAccept + " on service " + parameters.serviceName, parameters);
+        });
+    });
+
+    // To do: update the test data for enabling the annotation test
+    djstest.addFullTest(true, function metadataElementExtensionsTest() {
+        var csdlFile = "./endpoints/CustomAnnotations.xml";
+        var modifyTypeHttpClient = {};
+        var originalHttpClient = OData.defaultHttpClient;
+
+        // Modify the content-type of the response so that it is accepted by the metadataHandler.
+        // By default, the content-type of CustomAnnotations.xml comes back as text/xml
+        modifyTypeHttpClient.request = function (request, success, error) {
+            return originalHttpClient.request(request, function (response) {
+                response.headers["Content-Type"] = "application/xml";
+                success(response);
+            }, error);
+        }
+
+        OData.defaultHttpClient = modifyTypeHttpClient;
+
+        OData.read({ requestUri: csdlFile, headers: { Accept: "text/xml"} },
+            function (data) {
+                window.ODataReadOracle.readMetadata(csdlFile,
+                    function (expectedData) {
+                        djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                        djstest.done();
+                    }
+                )
+            },
+            unexpectedErrorHandler, OData.metadataHandler
+         );
+    });
+
+    djstest.addTest(function verifyNonDefaultReadMethodCalled() {
+        var endPoint = feed + "(0)";
+        djstest.assertsExpected(2);
+        OData.read(
+                { requestUri: endPoint },
+                function success(data, response) {
+                    djstest.assert(true, "Test executed");
+                    djstest.done();
+                },
+                null,
+                {
+                    read: function (response) {
+                        djstest.assert(true, "Non-default read reached");
+                        djstest.done();
+                    },
+                    accept: "*/*"
+                }
+            );
+    }, "Testing nondefault read is called.");
+
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-request-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-request-functional-tests.html b/JSLib/tests/odata-request-functional-tests.html
new file mode 100644
index 0000000..4298493
--- /dev/null
+++ b/JSLib/tests/odata-request-functional-tests.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>odata.request 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/ODataReadOracle.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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="common/djstest.js"></script>
+
+    <script type="text/javascript" src="odata-request-functional-tests.js"></script>
+</head>
+<body>
+    <h1 id="qunit-header">odata.request tests</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-request-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-request-functional-tests.js b/JSLib/tests/odata-request-functional-tests.js
new file mode 100644
index 0000000..5304459
--- /dev/null
+++ b/JSLib/tests/odata-request-functional-tests.js
@@ -0,0 +1,386 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;q=0.1";
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    var verifyRequest = function (request, done) {
+        if (request.method == "POST") {
+            verifyPost(request, done);
+        }
+        else if (request.method == "PUT") {
+            verifyPut(request, done);
+        }
+        else if (request.method == "PATCH") {
+            verifyPatch(request, done);
+        }
+    };
+
+    var tryRemoveOdataType = function (data) {
+        if (data && data["@odata.type"]) {
+            delete data["@odata.type"];
+        }
+
+        return data;
+    };
+
+    var verifyPost = function (request, done) {
+        var httpOperation = request.method + " " + request.requestUri;
+        djstest.log(httpOperation);
+        OData.request(request, function (data, response) {
+            djstest.log("Status code:" + response.statusCode);
+            djstest.assertAreEqual(response.statusCode, httpStatusCode.created, "Verify response code: " + httpOperation);
+            djstest.log("Uri:" + request.requestUri);
+            ODataReadOracle.readEntry(response.headers["Location"], function (expectedData) {
+                djstest.assertAreEqualDeep(response.data, expectedData, "Verify new entry against response: " + httpOperation);
+                done();
+            }, request.headers.Accept);
+        }, unexpectedErrorHandler);
+    };
+
+    var verifyPut = function(request, done) {
+        var httpOperation = request.method + " " + request.requestUri;
+        djstest.log(httpOperation);
+        OData.request(request, function(data, response) {
+            djstest.log("Status code:" + response.statusCode);
+            djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code: " + httpOperation);
+            djstest.log("Uri:" + request.requestUri);
+            ODataReadOracle.readEntry(request.requestUri, function(actualData) {
+                var requestData = tryRemoveOdataType(request.data);
+                djstest.assertAreEqualDeep(subset(actualData, requestData), requestData, "Verify updated entry: " + httpOperation);
+                done();
+            }, request.headers.Accept);
+        }, unexpectedErrorHandler);
+    };
+
+    var verifyPatch = function (request, done) {
+        var httpOperation = request.method + " " + request.requestUri;
+        djstest.log(httpOperation);
+        ODataReadOracle.readEntry(request.requestUri, function (originalData) {
+            OData.request(request, function (data, response) {
+                djstest.log("Status code:" + response.statusCode);
+                djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code");
+                djstest.log("Uri:" + request.requestUri);
+                ODataReadOracle.readEntry(request.requestUri, function (actualData) {
+
+                    // Merge the original data with the updated data to get the expected data
+                    var expectedData = $.extend(true, {}, originalData, request.data);
+                    djstest.assertAreEqualDeep(actualData, tryRemoveOdataType(expectedData), "Verify merged data");
+                    done();
+                }, request.headers["Content-Type"]);
+            }, unexpectedErrorHandler);
+        }, request.headers["Content-Type"]);
+    };
+
+    // Returns a subset of object with the same set of properties (recursive) as the subsetObject
+    var subset = function (object, subsetObject) {
+        if (typeof (object) == "object" && typeof (subsetObject) == "object") {
+            var result = {};
+            for (subsetProp in subsetObject) {
+                result[subsetProp] = subset(object[subsetProp], subsetObject[subsetProp]);
+            }
+            return result;
+        }
+        else {
+            return object;
+        }
+    };
+
+    var foodData = {
+        "@odata.type": "#DataJS.Tests.V4.Food",
+        FoodID: 42,
+        Name: "olive oil",
+        UnitPrice: 3.14,
+        ServingSize: 1,
+        MeasurementUnit: "",
+        ProteinGrams: 5,
+        FatGrams: 9,
+        CarbohydrateGrams: 2,
+        CaloriesPerServing: 6,
+        IsAvailable: true,
+        ExpirationDate: "2010-12-25T12:00:00Z",
+        ItemGUID: "27272727-2727-2727-2727-272727272727",
+        Weight: 10,
+        AvailableUnits: 1,
+        Packaging: {
+            Type: "Can",
+            Color: null,
+            NumberPerPackage: 1,
+            RequiresRefridgeration: false,
+            PackageDimensions: {
+                Length: 4,
+                Height: 3,
+                Width: 2,
+                Volume: 1
+            },
+            ShipDate: "2010-12-25T12:00:00Z"
+        }
+    };
+
+    var testServices = {
+        V4: "./endpoints/FoodStoreDataServiceV4.svc"
+    };
+
+    var testData = {
+        V4: $.extend(true, {}, foodData, {
+            AlternativeNames: ["name1", "name2"],
+            Providers:
+                    [{
+                        Name: "Provider",
+                        Aliases: ["alias1"],
+                        Details: {
+                            Telephone: "555-555-555",
+                            PreferredCode: 999
+                        }
+                    },
+                    {
+                        Name: "Provider2",
+                        Aliases: [],
+                        Details: null
+                    }
+                ]
+        })
+    };
+
+    var mimeTypes = [undefined, "application/json;odata.metadata=minimal"/*, "application/atom+xml"*/];
+
+    var httpStatusCode = {
+        created: 201,
+        noContent: 204,
+        notFound: 404
+    };
+
+    $.each(testServices, function (serviceName, service) {
+        var newFood = testData[serviceName];
+
+        var foodsFeed = service + "/Foods";
+        var categoriesFeed = service + "/Categories";
+
+        module("Functional", {
+            setup: function () {
+                djstest.log("Resetting data");
+                djstest.wait(function (done) {
+                    $.post(service + "/ResetData", done);
+                });
+            }
+        });
+
+        $.each(mimeTypes, function (_, mimeType) {
+            // Provide coverage for both undefined and specific DSVs
+            // For all other cases DSV = undefined is a valid scenario
+            var dataServiceVersions = ["4.0"];
+
+            $.each(dataServiceVersions, function (_, dataServiceVersion) {
+                var headers;
+                if (mimeType || dataServiceVersion) {
+                    headers = {
+                        "Content-Type": mimeType,
+                        Accept: mimeType,
+                        "OData-Version": dataServiceVersion
+                    };
+                }
+
+                djstest.addTest(function addEntityTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: {
+                            CategoryID: 42,
+                            Name: "New Category"
+                        }
+                    };
+
+                    djstest.assertsExpected(2);
+                    verifyRequest(request, djstest.done);
+                }, "Add new entity to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function addEntityWithUTF16CharTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: {
+                            CategoryID: 42,
+                            Name: "\u00f6 New Category \u00f1"
+                        }
+                    };
+
+                    djstest.assertsExpected(2);
+                    verifyRequest(request, djstest.done);
+                }, "Add new entity with UTF-16 character to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function addLinkedEntityTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed + "(0)/Foods",
+                        method: "POST",
+                        headers: headers,
+                        data: newFood
+                    };
+
+                    djstest.assertsExpected(2);
+                    verifyRequest(request, djstest.done);
+                }, "Add new linked entity to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function addEntityWithInlineFeedTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: {
+                            CategoryID: 42,
+                            Name: "Olive Products",
+                            Foods: [newFood]
+                        }
+                    };
+
+                    djstest.assertsExpected(3);
+                    verifyRequest(request, function () {
+                        ODataReadOracle.readEntry(foodsFeed + "(" + newFood.FoodID + ")", function (actualData) {
+                            djstest.assertAreEqual(actualData.Name, newFood.Name, "Verify inline entities were added");
+                            djstest.done();
+                        }, headers ? headers.Accept : undefined);
+                    });
+                }, "Add new entity with inline feed to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function addEntityWithInlineEntryTest(headers) {
+                    var request = {
+                        requestUri: foodsFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: $.extend({}, newFood, {
+                            Category: {
+                                CategoryID: 42,
+                                Name: "Olive Products"
+                            }
+                        })
+                    };
+
+                    djstest.assertsExpected(3);
+                    verifyRequest(request, function () {
+                        ODataReadOracle.readEntry(categoriesFeed + "(" + request.data.Category.CategoryID + ")", function (actualData) {
+                            djstest.assertAreEqual(actualData.Name, request.data.Category.Name, "Verify inline entities were added");
+                            djstest.done();
+                        }, headers ? headers.Accept : undefined);
+                    });
+                }, "Add new entity with inline entry to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function updateEntityTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed + "(0)",
+                        method: "PUT",
+                        headers: headers,
+                        data: {
+                            CategoryID: 0,
+                            Name: "Updated Category"
+                        }
+                    };
+
+                    djstest.assertsExpected(2);
+                    verifyRequest(request, djstest.done);
+                }, "Update entity to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                if (serviceName === "V4") {
+                    djstest.addTest(function updateEntityTest(headers) {
+                        var request = {
+                            requestUri: foodsFeed + "(0)",
+                            method: "PATCH",
+                            headers: headers,
+                            data: {
+                                "@odata.type": "#DataJS.Tests.V4.Food",
+                                AlternativeNames: ["one", "two"]
+                            }
+                        };
+
+                        djstest.assertsExpected(2);
+                        verifyRequest(request, djstest.done);
+                    }, "Update collection property to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+                }
+
+                if (mimeType !== "application/atom+xml") {
+                    djstest.addTest(function updatePrimitivePropertyTest(headers) {
+                        var request = {
+                            requestUri: categoriesFeed + "(0)/Name",
+                            method: "PUT",
+                            headers: headers,
+                            data: { value: "Updated Category" }
+                        };
+
+                        djstest.assertsExpected(2);
+                        verifyRequest(request, djstest.done);
+                    }, "Update primitive property to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+                }
+
+                djstest.addTest(function updateLinkedEntityTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed + "(0)/Foods(0)",
+                        method: "PUT",
+                        headers: headers,
+                        data: {
+                            "@odata.type": "#DataJS.Tests.V4.Food",
+                            Name: "Updated Food"
+                        }
+                    };
+
+                    djstest.assertsExpected(2);
+                    verifyRequest(request, djstest.done);
+                }, "Update linked entity to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function mergeEntityTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed + "(0)",
+                        method: "PATCH",
+                        headers: headers,
+                        data: { Name: "Merged Category" }
+                    };
+
+                    djstest.assertsExpected(2);
+                    verifyRequest(request, djstest.done);
+                }, "Merge entity to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function mergeLinkedEntityTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed + "(0)/Foods(0)",
+                        method: "PATCH",
+                        headers: headers,
+                        data: {
+                            "@odata.type": "#DataJS.Tests.V4.Food",
+                            Name: "Merged Food"
+                        }
+                    };
+
+                    djstest.assertsExpected(2);
+                    verifyRequest(request, djstest.done);
+                }, "Merge linked entity to " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+
+                djstest.addTest(function deleteEntityTest(headers) {
+                    var endpoint = categoriesFeed + "(0)";
+                    djstest.assertsExpected(2);
+                    OData.request({
+                        requestUri: endpoint,
+                        method: "DELETE",
+                        headers: headers
+                    }, function (data, response) {
+                        djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code");
+                        $.ajax({
+                            url: endpoint,
+                            error: function (xhr) {
+                                djstest.assertAreEqual(xhr.status, httpStatusCode.notFound, "Verify response code of attempted retrieval after delete");
+                                djstest.done();
+                            },
+                            success: function () {
+                                djstest.fail("Delete failed: querying the endpoint did not return expected response code");
+                                djstest.done();
+                            }
+                        });
+                    }, unexpectedErrorHandler);
+                }, "Delete entity from " + serviceName + " service using mimeType = " + mimeType + " and DSV = " + dataServiceVersion, headers);
+            });
+        });
+    });
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-roundtrip-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-roundtrip-functional-tests.js b/JSLib/tests/odata-roundtrip-functional-tests.js
new file mode 100644
index 0000000..a8682f3
--- /dev/null
+++ b/JSLib/tests/odata-roundtrip-functional-tests.js
@@ -0,0 +1,374 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    var verifyRequest = function (request, done) {
+        if (request.method == "POST") {
+            if (request.headers && request.headers["X-HTTP-Method"] == "MERGE") {
+                verifyMerge(request, done);
+            }
+            else {
+                verifyPost(request, done);
+            }
+        }
+        else if (request.method == "PUT") {
+            verifyPut(request, done);
+        }
+    };
+
+    var verifyPost = function (request, done) {
+        var httpOperation = request.method + " " + request.requestUri;
+        OData.request(request, function (data, response) {
+            djstest.assertAreEqual(response.statusCode, httpStatusCode.created, "Verify response code: " + httpOperation);
+            ODataReadOracle.readJson(data.__metadata.uri, function (expectedData) {
+                djstest.assertAreEqualDeep(response.data, expectedData, "Verify new entry against response: " + httpOperation);
+                done();
+            }, request.headers.Accept);
+        }, unexpectedErrorHandler);
+    };
+
+    var verifyPut = function (request, done) {
+        var httpOperation = request.method + " " + request.requestUri;
+        OData.request(request, function (data, response) {
+            djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code: " + httpOperation);
+            ODataReadOracle.readJson(request.requestUri, function (actualData) {
+                djstest.assertAreEqualDeep(actualData, request.data, "Verify updated entry: " + httpOperation);
+                done();
+            }, request.headers.Accept);
+        }, unexpectedErrorHandler);
+    }
+
+    var verifyMerge = function (request, done) {
+        var httpOperation = request.method + " " + request.requestUri;
+        ODataReadOracle.readJson(request.requestUri, function (originalData) {
+            OData.request(request, function (data, response) {
+                djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code");
+                ODataReadOracle.readJson(request.requestUri, function (actualData) {
+                    // Merge the original data with the updated data to get the expected data
+                    var expectedData = $.extend(true, {}, originalData, request.data);
+                    djstest.assertAreEqualDeep(actualData, expectedData, "Verify merged data");
+                    done();
+                }, request.headers["Content-Type"]);
+            }, unexpectedErrorHandler);
+        }, request.headers["Content-Type"]);
+    }
+
+    // Returns a subset of object with the same set of properties (recursive) as the subsetObject
+    var subset = function (object, subsetObject) {
+        if (typeof (object) == "object" && typeof (subsetObject) == "object") {
+            var result = {};
+            for (subsetProp in subsetObject) {
+                result[subsetProp] = subset(object[subsetProp], subsetObject[subsetProp]);
+            }
+            return result;
+        }
+        else {
+            return object;
+        }
+    };
+
+    var service = "./endpoints/FoodStoreDataService.svc";
+    var foodsFeed = service + "/Foods";
+    var categoriesFeed = service + "/Categories";
+    //var mimeTypes = [undefined, "application/json", "application/atom+xml"];
+    var mimeTypes = ["application/json", "application/atom+xml"];
+
+    var httpStatusCode = {
+        created: 201,
+        noContent: 204,
+        notFound: 404
+    };
+
+    var newFood = {
+        "__metadata": {
+            type: "DataJS.Tests.Food"
+        },
+        FoodID: 42,
+        Name: "olive oil",
+        UnitPrice: 3.14,
+        ServingSize: "1",
+        MeasurementUnit: "Cup",
+        ProteinGrams: 5,
+        FatGrams: 9,
+        CarbohydrateGrams: 2,
+        CaloriesPerServing: "6",
+        IsAvailable: true,
+        ExpirationDate: new Date("2011/05/03 12:00:00 PM"),
+        ItemGUID: "27272727-2727-2727-2727-272727272727",
+        Weight: 10,
+        AvailableUnits: 1,
+        Packaging: {
+            Type: "Can",
+            Color: "White",
+            NumberPerPackage: 1,
+            RequiresRefridgeration: false,
+            PackageDimensions: {
+                Length: "4",
+                Height: 3,
+                Width: "2",
+                Volume: 1
+            },
+            ShipDate: new Date("2011/01/01 12:00:00 PM")
+        }
+    };
+
+    var newFoodLinks = {
+        uri: foodsFeed + "(1)"
+    }
+
+    module("Functional", {
+        setup: function () {
+            $.ajax({ async: false, type: "POST", url: service + "/ResetData" });
+        }
+    });
+
+    $.each(mimeTypes, function (_, mimeType) {
+        var headers = mimeType ? { "Content-Type": mimeType, Accept: mimeType} : undefined;
+
+                djstest.addTest(function addEntityTest(headers) {
+                    var request = {
+                        requestUri: categoriesFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: {
+                            CategoryID: 42,
+                            Name: "New Category"
+                        }
+                    };
+
+                    verifyRequest(request, function () {
+                        OData.read({ requestUri: categoriesFeed + "(42)", headers: { Accept: mimeType} }, function (actualData, response) {
+                            actualData.CategoryID = 27;
+                            var newRequest = {
+                                requestUri: categoriesFeed,
+                                method: "POST",
+                                headers: headers,
+                                data: actualData
+                            };
+                            verifyRequest(newRequest, function () { djstest.done(); });
+                        }, request.headers["Content-Type"]);
+                    });
+
+                }, "Post, read posted data, post read data (mimeType = " + mimeType + ")", headers);
+
+        djstest.addTest(function addLinkedEntityTest(headers) {
+            var request = {
+                requestUri: categoriesFeed + "(0)/Foods",
+                method: "POST",
+                headers: headers,
+                data: newFood
+            };
+
+            verifyRequest(request, function () {
+                OData.read({ requestUri: categoriesFeed + "(0)/Foods(42)", headers: { Accept: mimeType} }, function (actualData, response) {
+                    actualData.FoodID = 94;
+                    var newRequest = {
+                        requestUri: categoriesFeed + "(0)/Foods",
+                        method: "POST",
+                        headers: headers,
+                        data: actualData
+                    };
+                    verifyRequest(newRequest, function () { djstest.done(); });
+                }, request.headers["Content-Type"]);
+            });
+        }, "POST, read, POST an entry " + mimeType + ")", headers);
+
+
+        djstest.addTest(function addLinkedEntityTest(headers) {
+            var request = {
+                requestUri: categoriesFeed + "(0)/Foods(0)",
+                method: "PUT",
+                headers: headers,
+                data: newFood
+            };
+
+            verifyRequest(request, function () {
+                OData.read({ requestUri: categoriesFeed + "(0)/Foods(0)", headers: { Accept: mimeType} }, function (actualData, response) {
+                    var newRequest = {
+                        requestUri: categoriesFeed + "(0)/Foods(0)",
+                        method: "PUT",
+                        headers: headers,
+                        data: {
+                            "__metadata": { type: "DataJS.Tests.Food" },
+                            Name: "New Food" 
+                        }
+                    };
+                    verifyRequest(newRequest, function () { djstest.done(); });
+                });
+            });
+        }, "PUT, read, PUT a new linked entry " + mimeType + ")", headers);
+
+        djstest.addTest(function addEntityWithInlineFeedTest(headers) {
+            var request = {
+                requestUri: categoriesFeed,
+                method: "POST",
+                headers: headers,
+                data: {
+                    CategoryID: 42,
+                    Name: "Olive Products",
+                    Foods: [newFood]
+                }
+            };
+
+            verifyRequest(request, function () {
+                OData.read({ requestUri: foodsFeed + "(" + newFood.FoodID + ")", headers: { Accept: mimeType} }, function (actualData, response) {
+                    var newRequest = {
+                        requestUri: categoriesFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: {
+                            CategoryID: 27,
+                            Name: "Olive Products",
+                            Foods: [actualData]
+                        }
+                    };
+                    verifyRequest(newRequest, function () { djstest.done(); });
+                });
+            });
+
+        }, "POST, read, POST an entity with inline feed " + mimeType + ")", headers);
+
+        djstest.addTest(function addEntityWithInlineEntryTest(headers) {
+            var request = {
+                requestUri: foodsFeed,
+                method: "POST",
+                headers: headers,
+                data: $.extend({}, newFood, {
+                    Category: {
+                        "__metadata": { uri: "" },
+                        CategoryID: 42,
+                        Name: "Olive Products"
+                    }
+                })
+            };
+
+            verifyRequest(request, function () {
+                OData.read({ requestUri: foodsFeed + "(" + newFood.FoodID + ")", headers: { Accept: mimeType} }, function (actualData, response) {
+                    actualData.FoodID = 76;
+                    var newRequest = {
+                        requestUri: foodsFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: $.extend({}, actualData, {
+                            Category: {
+                                "__metadata": { uri: "" },
+                                CategoryID: 27,
+                                Name: "Olive Products"
+                            }
+                        })
+                    };
+                    verifyRequest(newRequest, function () { djstest.done(); });
+                });
+            });
+        }, "Add new entity with inline entry (mimeType = " + mimeType + ")", headers);
+
+        djstest.addTest(function addEntityTest(headers) {
+            var request = {
+                requestUri: categoriesFeed + "(1)",
+                method: "PUT",
+                headers: headers,
+                data: {
+                    CategoryID: 1,
+                    Name: "New Category"
+                }
+            };
+
+            verifyRequest(request, function () {
+                OData.read({ requestUri: categoriesFeed + "(1)", headers: { Accept: mimeType} }, function (actualData, response) {
+                    actualData.CategoryID = 2;
+                    var newRequest = {
+                        requestUri: categoriesFeed + "(2)",
+                        method: "PUT",
+                        headers: headers,
+                        data: actualData
+                    };
+                    verifyRequest(newRequest, function () { djstest.done(); });
+                }, request.headers["Content-Type"]);
+            });
+
+        }, "Put, read put data, put read data (mimeType = " + mimeType + ")", headers);
+
+        djstest.addTest(function addEntityTest(headers) {
+            OData.read({ requestUri: foodsFeed + "(0)", headers: { Accept: mimeType} },
+                function (actualData, response) {
+                    actualData.CategoryID = 216;
+                    var request = {
+                        requestUri: foodsFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: actualData
+                    };
+                    verifyRequest(request,
+                        function () {
+                            OData.read({ requestUri: foodsFeed + "(216)", headers: { Accept: mimeType} },
+                                function (data, response) {
+                                    ODataReadOracle.readJson(foodsFeed + "(216)",
+                                        function (expectedData) {
+                                            djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                            djstest.done();
+                                        });
+                                });
+                        });
+                });
+        }, "Read data with dates, post read data with dates to new ID, read new ID data with dates" + mimeType + ")", headers);
+
+        djstest.addTest(function addEntityTest(headers) {
+            OData.read({ requestUri: categoriesFeed + "(0)", headers: { Accept: mimeType} }, 
+                function (actualData, response) {
+                    actualData.CategoryID = 81;
+                    var request = {
+                        requestUri: categoriesFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: actualData
+                    };
+                    verifyRequest(request, 
+                        function () { 
+                            OData.read({ requestUri: categoriesFeed + "(81)", headers: { Accept: mimeType} }, 
+                                function (data, response) {
+                                    ODataReadOracle.readJson(categoriesFeed + "(81)",
+                                        function (expectedData) {
+                                            djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                            djstest.done();
+                                        }
+                                    );
+                                }
+                            );
+                        }
+                    );
+                }
+            );
+        }, "Read existing data, post existing data to new idea, read new ID data" + mimeType + ")", headers);
+
+
+        djstest.addTest(function addEntityTest(headers) {
+            OData.read({ requestUri: categoriesFeed + "(0)", headers: { Accept: mimeType} },
+                function (actualData, response) {
+                    actualData.CategoryID = 81;
+                    var request = {
+                        requestUri: categoriesFeed,
+                        method: "POST",
+                        headers: headers,
+                        data: actualData
+                    };
+                    verifyRequest(request,
+                        function () {
+                            OData.read({ requestUri: categoriesFeed + "(81)", headers: { Accept: mimeType} },
+                                function (data, response) {
+                                    ODataReadOracle.readJson(categoriesFeed + "(81)",
+                                        function (expectedData) {
+                                            djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                            djstest.done();
+                                        });
+                                });
+                        });
+                });
+        }, "Read existing data, post existing data to new idea, read new ID data" + mimeType + ")", headers);
+    });
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-tests.js b/JSLib/tests/odata-tests.js
new file mode 100644
index 0000000..c3724a6
--- /dev/null
+++ b/JSLib/tests/odata-tests.js
@@ -0,0 +1,306 @@
+/// <reference path="../src/odata-net.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/djstest.js" />
+/// <reference path="common/mockHttpClient.js" />
+
+// odata-tests.js
+(function (window, undefined) {
+    var northwindService = "http://services.odata.org/Northwind/Northwind.svc/";
+    var localFeed = "./endpoints/FoodStoreDataService.svc/Foods";
+    var northwindFeed = northwindService + "Suppliers";
+
+    var countIFrames = function () {
+        /// <summary>Count the number of IFRAMES in the page</summary>
+        /// <returns type="Integer">The number of IFRAMES</returns>
+        return document.getElementsByTagName("IFRAME").length;
+    }
+
+    module("Unit");
+
+    var originalEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+
+    var restoreJsonpCallback = function () {
+        /// <summary>Restores OData.defaultHttpClient.enableJsonpCallback to the library default.</summary>
+        OData.defaultHttpClient.enableJsonpCallback = originalEnableJsonpCallback;
+    };
+
+    djstest.addTest(function checkApiTest() {
+        var internals = window.OData.canUseJSONP !== undefined;
+        if (internals) {
+            // Don't even bother - there is a very long list for inter-module communication.
+            // {targetName: "OData", names: "..." }
+            djstest.pass("Do not test public api's when internals are visible");
+        } else {
+            var apis = [
+                { targetName: "datajs", names: "createDataCache,createStore,defaultStoreMechanism" },
+                { targetName: "OData", names: "atomHandler,batchHandler,defaultError,defaultHandler,defaultHttpClient,defaultMetadata,defaultSuccess,jsonHandler,metadataHandler,read,request,textHandler,xmlHandler,parseMetadata" }
+            ];
+
+            for (var i = 0; i < apis.length; i++) {
+                var target = window[apis[i].targetName];
+
+                var actuals = [];
+                for (var actual in target) {
+                    actuals.push(actual);
+                }
+
+                actuals.sort();
+
+                var names = apis[i].names.split(",");
+                names.sort();
+
+                djstest.assertAreEqual(actuals.join(), names.join(), "actual names for " + apis[i].targetName);
+            }
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function simpleLocalReadTest() {
+        OData.read(localFeed, function (data, request) {
+            djstest.assert(data !== null, "data !== null");
+            djstest.assert(request !== null, "request !== null");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function simpleLocalReadWithRequestTest() {
+        OData.read({ requestUri: localFeed, headers: { Accept: "application/json"} }, function (data, response) {
+            djstest.assert(data !== null, "data !== null");
+            djstest.assert(response !== null, "response !== null");
+            djstest.assertAreEqual(data, response.data, "data === response.data");
+
+            // Typically application/json;charset=utf-8, but browser may change the request charset (and thus response).
+            var contentType = response.headers["Content-Type"];
+            contentType = contentType.split(';')[0];
+            djstest.assertAreEqual(contentType, "application/json", 'contentType === "application/json"');
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function simpleReadTest() {
+        var oldEnableJsonpCallback = OData.defaultHttpClient.enableJsonpCallback;
+        OData.defaultHttpClient.enableJsonpCallback = true;
+
+        var iframesBefore = countIFrames();
+        OData.read(northwindService + "Regions", function (data, request) {
+            djstest.assert(data !== null, "data !== null");
+            djstest.assert(request !== null, "request !== null");
+
+            // IFRAME recycling does not work in Opera because as soon as the IFRAME is added to the body, all variables
+            // go out of scope
+            if (!window.opera) {
+                djstest.assertAreEqual(countIFrames() - iframesBefore, 0, "extra IFRAMEs (baseline: " + iframesBefore + ")");
+            }
+
+            OData.defaultHttpClient.enableJsonpCallback = oldEnableJsonpCallback;
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function simpleReadWithParamsTest() {
+        OData.defaultHttpClient.enableJsonpCallback = true;
+        OData.read(northwindFeed + "?$top=3", function (data, request) {
+            djstest.assert(data !== null, "data !== null");
+            djstest.assert(request !== null, "request !== null");
+            restoreJsonpCallback();
+            djstest.done();
+        }, djstest.failAndDoneCallback("Unable to read from " + northwindFeed, restoreJsonpCallback));
+    });
+
+    djstest.addTest(function simpleReadWithNoParamsTest() {
+        OData.defaultHttpClient.enableJsonpCallback = true;
+        OData.read(northwindFeed + "?", function (data, request) {
+            djstest.assert(data !== null, "data !== null");
+            djstest.assert(request !== null, "request !== null");
+            restoreJsonpCallback();
+            djstest.done();
+        }, djstest.failAndDoneCallback("Unable to read from " + northwindFeed, restoreJsonpCallback));
+    });
+
+    djstest.addTest(function jsonpTimeoutTest() {
+        // Verifies that JSONP will timeout, and that the
+        // enableJsonpCallback flag can be set on the request itself.
+        var iframesBefore = countIFrames();
+        OData.request({
+            requestUri: northwindFeed + "?$fail=true",
+            timeoutMS: 100,
+            enableJsonpCallback: true
+        }, function (data, request) {
+            djstest.fail("expected an error callback");
+            djstest.done();
+        }, function (err) {
+            djstest.assert(err.message.indexOf("timeout") !== 1, "err.message[" + err.message + "].indexOf('timeout') !== 1");
+            djstest.assertAreEqual(countIFrames() - iframesBefore, 0, "extra script tags (baseline: " + iframesBefore + ")");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function requestDefaultsTest() {
+        // Save current defaults.
+        var oldError = OData.defaultError;
+        var oldSuccess = OData.defaultSuccess;
+        var oldDefaultHandler = OData.defaultHandler;
+        var oldHttpClient = OData.defaultHttpClient;
+
+        OData.defaultSuccess = function (data, response) {
+            djstest.assertAreEqual(response.statusCode, 299, "success method reached when expected");
+        };
+
+        OData.defaultError = function (error) {
+            var response = error.response;
+            djstest.assertAreEqual(response.statusCode, 500, "error method reached when expected");
+        };
+
+        OData.defaultHandler = {
+            read: function (response) {
+                djstest.assertAreEqual(response.statusCode, 299, "default handler read method reached when expected");
+            },
+            accept: "test accept string"
+        };
+
+        OData.defaultHttpClient = MockHttpClient.clear();
+
+        var testUris = [
+            "requestDefaultsTest/request",
+            "requestDefaultsTest/request1",
+            "requestDefaultsTest/error"
+        ];
+
+        MockHttpClient.addRequestVerifier(testUris[0], function (request) {
+            djstest.assertAreEqual(request.method, "GET", "request.method is GET");
+            djstest.assert(request.headers, "request.headers is defined and not null");
+            djstest.assertAreEqual(request.headers.Accept, "test accept string");
+        });
+
+        MockHttpClient.addResponse(testUris[1], { statusCode: 299, body: "test response" });
+        MockHttpClient.addResponse(testUris[2], { statusCode: 500, body: "error response" });
+
+        try {
+            var i, len;
+            for (i = 0, len = testUris.length; i < len; i++) {
+                OData.request({ requestUri: testUris[i] });
+            }
+        }
+        finally {
+            // Restore defaults.
+            OData.defaultError = oldError;
+            OData.defaultSuccess = oldSuccess;
+            OData.defaultHandler = oldDefaultHandler;
+            OData.defaultHttpClient = oldHttpClient;
+        }
+
+        djstest.assertsExpected(6);
+        djstest.done();
+    });
+
+    djstest.addTest(function requestUpdateTest() {
+        // Save current defaults.
+        var testHandler = {
+            read: function (response) {
+                response.data = response.body;
+            },
+            write: function (request) {
+                djstest.assertAreEqual(request.method, "POST", "handler write method, request has the correct method");
+            }
+        };
+
+        var testSuccess = function (data, response) {
+            djstest.assertAreEqual(data, "test response", "success callback has the correct data");
+            djstest.assertAreEqual(response.status, 200, "success method reached when expected");
+        };
+
+        var testError = function (error) {
+            var response = error.response;
+            djstest.assertAreEqual(response.status, 500, "error method reached when expected");
+        };
+
+        MockHttpClient.addResponse("requestUpdateTest", { status: 200, body: "test response" });
+        MockHttpClient.addResponse("requestUpdateTest", { status: 500, body: "error response" });
+
+        OData.request({ requestUri: "requestUpdateTest", method: "POST" }, testSuccess, testError, testHandler, MockHttpClient);
+
+        djstest.done();
+    });
+
+    djstest.addTest(function parseMetadataTest() {
+        var metadata = '<?xml version="1.0" encoding="utf-8"?>' +
+            '<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">' +
+            '<edmx:DataServices m:DataServiceVersion="4.0" m:MaxDataServiceVersion="4.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">' +
+            '<Schema Namespace="ODataDemo" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">' +
+                '<EntityType Name="Product">' +
+                    '<Key><PropertyRef Name="ID" /></Key>' +
+                    '<Property Name="ID" Type="Edm.Int32" Nullable="false" />' +
+                    '<Property Name="Name" Type="Edm.String" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="false" />' +
+                    '<Property Name="Description" Type="Edm.String" m:FC_TargetPath="SyndicationSummary" m:FC_ContentKind="text" m:FC_KeepInContent="false" />' +
+                    '<Property Name="ReleaseDate" Type="Edm.DateTime" Nullable="false" />' +
+                    '<Property Name="DiscontinuedDate" Type="Edm.DateTime" />' +
+                    '<Property Name="Rating" Type="Edm.Int32" Nullable="false" />' +
+                    '<Property Name="Price" Type="Edm.Decimal" Nullable="false" />' +
+                    '<NavigationProperty Name="Category" Relationship="ODataDemo.Product_Category_Category_Products" ToRole="Category_Products" FromRole="Product_Category" />' +
+                '</EntityType>' +
+                '<EntityType Name="Category">' +
+                    '<Key>' +
+                        '<PropertyRef Name="ID" />' +
+                    '</Key>' +
+                    '<Property Name="ID" Type="Edm.Int32" Nullable="false" />' +
+                    '<Property Name="Name" Type="Edm.String" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="true" />' +
+                    '<NavigationProperty Name="Products" Relationship="ODataDemo.Product_Category_Category_Products" ToRole="Product_Category" FromRole="Category_Products" />' +
+                '</EntityType>' +
+                '<Association Name="Product_Category_Category_Products"><End Type="ODataDemo.Category" Role="Category_Products" Multiplicity="0..1" />' +
+                    '<End Type="ODataDemo.Product" Role="Product_Category" Multiplicity="*" />' +
+                '</Association>' +
+                '<EntityContainer Name="DemoService" m:IsDefaultEntityContainer="true">' +
+                    '<EntitySet Name="Products" EntityType="ODataDemo.Product" />' +
+                    '<EntitySet Name="Categories" EntityType="ODataDemo.Category" />' +
+                    '<FunctionImport Name="Discount" IsBindable="true" m:IsAlwaysBindable="true">' +
+                        '<Parameter Name="product" Type="ODataDemo.Product" />' +
+                        '<Parameter Name="discountPercentage" Type="Edm.Int32" Nullable="false" />' +
+                    '</FunctionImport>' +
+                    '<AssociationSet Name="Products_Category_Categories" Association="ODataDemo.Product_Category_Category_Products">' +
+                        '<End Role="Product_Category" EntitySet="Products" />' +
+                        '<End Role="Category_Products" EntitySet="Categories" />' +
+                    '</AssociationSet>' +
+                '</EntityContainer>' +
+             '</Schema></edmx:DataServices></edmx:Edmx>';
+
+        var parsedMetadata = OData.parseMetadata(metadata);
+        var expected =
+        {
+            "version": "1.0",
+            "dataServices":
+            {
+                "maxDataServiceVersion": "4.0",
+                "dataServiceVersion": "4.0",
+                "schema": [
+                    {
+                        "namespace": "ODataDemo",
+                        "entityType": [
+                            {
+                                "name": "Product",
+                                "key": { "propertyRef": [{ "name": "ID"}] },
+                                "property": [
+                                    { "name": "ID", "nullable": "false", "type": "Edm.Int32" },
+                                    { "name": "Name", "type": "Edm.String", "FC_KeepInContent": "false", "FC_ContentKind": "text", "FC_TargetPath": "SyndicationTitle" },
+                                    { "name": "Description", "type": "Edm.String", "FC_KeepInContent": "false", "FC_ContentKind": "text", "FC_TargetPath": "SyndicationSummary" },
+                                    { "name": "ReleaseDate", "nullable": "false", "type": "Edm.DateTime" }, { "name": "DiscontinuedDate", "type": "Edm.DateTime" },
+                                    { "name": "Rating", "nullable": "false", "type": "Edm.Int32" }, { "name": "Price", "nullable": "false", "type": "Edm.Decimal"}],
+                                "navigationProperty": [
+                                    { "name": "Category", "fromRole": "Product_Category", "toRole": "Category_Products", "relationship": "ODataDemo.Product_Category_Category_Products" }
+                                ]
+                            }, {
+                                "name": "Category",
+                                "key": { "propertyRef": [{ "name": "ID"}] },
+                                "property": [{ "name": "ID", "nullable": "false", "type": "Edm.Int32" }, { "name": "Name", "type": "Edm.String", "FC_KeepInContent": "true", "FC_ContentKind": "text", "FC_TargetPath": "SyndicationTitle"}],
+                                "navigationProperty": [{ "name": "Products", "fromRole": "Category_Products", "toRole": "Product_Category", "relationship": "ODataDemo.Product_Category_Category_Products"}]
+                            }],
+                        "association": [{ "name": "Product_Category_Category_Products", "end": [{ "type": "ODataDemo.Category", "multiplicity": "0..1", "role": "Category_Products" }, { "type": "ODataDemo.Product", "multiplicity": "*", "role": "Product_Category"}]}],
+                        "entityContainer": [{ "name": "DemoService", "isDefaultEntityContainer": "true", "entitySet": [{ "name": "Products", "entityType": "ODataDemo.Product" }, { "name": "Categories", "entityType": "ODataDemo.Category"}], "functionImport": [{ "name": "Discount", "isAlwaysBindable": "true", "isBindable": "true", "parameter": [{ "name": "product", "type": "ODataDemo.Product" }, { "name": "discountPercentage", "nullable": "false", "type": "Edm.Int32"}]}], "associationSet": [{ "name": "Products_Category_Categories", "association": "ODataDemo.Product_Category_Category_Products", "end": [{ "role": "Product_Category", "entitySet": "Products" }, { "role": "Category_Products", "entitySet": "Categories"}]}]}]
+                    }]
+            }
+        };
+        djstest.assertAreEqualDeep(expected, parsedMetadata, "metadata should be parsed to datajs format");
+        djstest.done();
+    });
+
+})(this);


[04/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-json-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-json-tests.js b/JSLib/tests/odata-json-tests.js
new file mode 100644
index 0000000..775b0d0
--- /dev/null
+++ b/JSLib/tests/odata-json-tests.js
@@ -0,0 +1,888 @@
+/// <reference path="../src/odata-net.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="../common/djstestfx.js" />
+
+// odata-tests.js
+
+(function (window, undefined) {
+
+    // DATAJS INTERNAL START
+    djstest.addTest(function isArrayTest() {
+        djstest.assert(datajs.isArray([]));
+        djstest.assert(datajs.isArray([1, 2]));
+        djstest.assert(!datajs.isArray({}));
+        djstest.assert(!datajs.isArray("1,2,3,4"));
+        djstest.assert(!datajs.isArray());
+        djstest.assert(!datajs.isArray(null));
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonParserTest() {
+        var tests = [
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" }, expected: {} },
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                expected: {
+                    "@odata.context": "http://foo/OData.svc/$metadata",
+                    value: [
+                      {
+                          name: "Products",
+                          kind: "EntitySet",
+                          url: "Products"
+                      },
+                      {
+                          name: "ProductDetails",
+                          kind: "EntitySet",
+                          url: "ProductDetails"
+                      }
+                  ]
+                }
+            },
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                expected: {
+                    value: [
+                      {
+                          name: "Products",
+                          kind: "EntitySet",
+                          url: "http://foo/OData.svc/Products"
+                      },
+                      {
+                          name: "ProductDetails",
+                          kind: "EntitySet",
+                          url: "http://foo/OData.svc/ProductDetails"
+                      }
+                  ]
+                }
+            },
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" }, expected: { "@odata.context": "http://foo/OData.svc/$metadata#Products(0)/Name", value: "Bread"} },
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                expected: {
+                    "@odata.context": "http://foo/OData.svc/$metadata#Products",
+                    value: [
+                      {
+                          "@odata.type": "#ODataDemo.Product",
+                          "@odata.id": "http://foo/OData.svc/Products(0)",
+                          "@odata.editLink": "Products(0)",
+                          "Categories@odata.navigationLink": "Products(0)/Categories",
+                          "Categories@odata.associationLink": "Products(0)/Categories/$ref",
+                          "Supplier@odata.navigationLink": "Products(0)/Supplier",
+                          "Supplier@odata.associationLink": "Products(0)/Supplier/$ref",
+                          "ProductDetail@odata.navigationLink": "Products(0)/ProductDetail",
+                          "ProductDetail@odata.associationLink": "Products(0)/ProductDetail/$ref",
+                          ID: 0,
+                          Name: "Bread",
+                          Description: "Whole grain bread",
+                          "ReleaseDate@odata.type": "#DateTimeOffset",
+                          ReleaseDate: "1992-01-01T00:00:00Z",
+                          DiscontinuedDate: null,
+                          "Rating@odata.type": "#Int16",
+                          Rating: 4,
+                          Price: 2.5
+                      }]
+                }
+            },
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                expected: {
+                    "@odata.context": "http://foo/OData.svc/$metadata#Products",
+                    value: [
+                      {
+                          ID: 0,
+                          Name: "Bread",
+                          Description: "Whole grain bread",
+                          ReleaseDate: "1992-01-01T00:00:00Z",
+                          DiscontinuedDate: null,
+                          Rating: 4,
+                          Price: 2.5
+                      }]
+                }
+            },
+             { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                 expected: {
+                     value: [
+                      {
+                          ID: 0,
+                          Name: "Bread",
+                          Description: "Whole grain bread",
+                          ReleaseDate: "1992-01-01T00:00:00Z",
+                          DiscontinuedDate: null,
+                          Rating: 4,
+                          Price: 2.5
+                      }]
+                 }
+             },
+              { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                  expected: {
+                      "@odata.context": "http://foo/OData.svc/$metadata#Products/$entry",
+                      "@odata.type": "#ODataDemo.Product",
+                      "@odata.id": "http://foo/OData.svc/Products(0)",
+                      "@odata.editLink": "Products(0)",
+                      "Categories@odata.navigationLink": "Products(0)/Categories",
+                      "Categories@odata.associationLink": "Products(0)/Categories/$ref",
+                      "Supplier@odata.navigationLink": "Products(0)/Supplier",
+                      "Supplier@odata.associationLink": "Products(0)/Supplier/$ref",
+                      "ProductDetail@odata.navigationLink": "Products(0)/ProductDetail",
+                      "ProductDetail@odata.associationLink": "Products(0)/ProductDetail/$ref",
+                      ID: 0,
+                      Name: "Bread",
+                      Description: "Whole grain bread",
+                      "ReleaseDate@odata.type": "#DateTimeOffset",
+                      ReleaseDate: "1992-01-01T00:00:00Z",
+                      DiscontinuedDate: null,
+                      "Rating@odata.type": "#Int16",
+                      Rating: 4,
+                      Price: 2.5
+                  }
+              },
+              { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                  expected: {
+                      "@odata.context": "http://foo/OData.svc/$metadata#Products/$entry",
+                      ID: 0,
+                      Name: "Bread",
+                      Description: "Whole grain bread",
+                      ReleaseDate: "1992-01-01T00:00:00Z",
+                      DiscontinuedDate: null,
+                      Rating: 4,
+                      Price: 2.5
+                  }
+              },
+              { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                  expected: {
+                      ID: 0,
+                      Name: "Bread",
+                      Description: "Whole grain bread",
+                      ReleaseDate: "1992-01-01T00:00:00Z",
+                      DiscontinuedDate: null,
+                      Rating: 4,
+                      Price: 2.5
+                  }
+              },
+              { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                  expected: {
+                      "@odata.context": "http://foo/$metadata#Customer(-10)/PrimaryContactInfo/AlternativeNames",
+                      "@odata.type": "#Collection(String)",
+                      value: [
+                      "グぁマせぺネソぁぼソひバたぴソ歹九ネボボяポソ畚クяせべ歹珱Я欲タハバミ裹ぼボをヲ歹んひ九ひ匚ぁa",
+                      "qckrnuruxcbhjfimnsykgfquffobcadpsaocixoeljhspxrhebkudppgndgcrlyvynqhbujrnvyxyymhnroemigogsqulvgallta",
+                      "btsnhqrjqryqzgxducl",
+                      "qbtlssjhunufmzdv",
+                      "ボんЯぜチべゼボボほa匚ミぼ九ぁひチ珱黑ミんぁタび暦クソソボゾんんあゼぞひタボタぜん弌ひべ匚",
+                      "vicqasfdkxsuyuzspjqunxpyfuhlxfhgfqnlcpdfivqnxqoothnfsbuykfguftgulgldnkkzufssbae",
+                      "九ソミせボぜゾボёaをぜЯまゾタぜタひ縷ダんaバたゼソ",
+                      "ぽマタぁぁ黑ソゼミゼ匚zソダマぁァゾぽミaタゾ弌ミゼタそzぺポせ裹バポハハヲぺチあマ匚ミ",
+                      "hssiißuamtctgqhglmusexyikhcsqctusonubxorssyizhyqpbtbdßjnelxqttkhdalabibuqhiubtßsptrmzelud",
+                      "gbjssllxzzxkmßppyyrhgmoeßizlcmsuqqnvjßudszevtfunflqzqcuubukypßqjcix"
+                     ]
+                  }
+              }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var data = window.JSON.stringify(tests[i].expected);
+            var actual = OData.jsonParser(OData.jsonHandler, data, tests[i].context);
+            djstest.assertAreEqualDeep(actual, tests[i].expected, "test " + i + "didn't return the expected data");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function jsonSerializerTest() {
+        var tests = [
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" }, expected: { value: ""} },
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" }, expected: { value: []} },
+            { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+                expected: {
+                    ID: 0,
+                    Name: "Bread",
+                    Description: "Whole grain bread",
+                    ReleaseDate: "1992-01-01T00:00:00Z",
+                    DiscontinuedDate: null,
+                    Rating: 4,
+                    Price: 2.5
+                }
+            },
+           { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+               expected: {
+                   value: [
+                      "グぁマせぺネソぁぼソひバたぴソ歹九ネボボяポソ畚クяせべ歹珱Я欲タハバミ裹ぼボをヲ歹んひ九ひ匚ぁa",
+                      "qckrnuruxcbhjfimnsykgfquffobcadpsaocixoeljhspxrhebkudppgndgcrlyvynqhbujrnvyxyymhnroemigogsqulvgallta",
+                      "btsnhqrjqryqzgxducl",
+                      "qbtlssjhunufmzdv",
+                      "ボんЯぜチべゼボボほa匚ミぼ九ぁひチ珱黑ミんぁタび暦クソソボゾんんあゼぞひタボタぜん弌ひべ匚",
+                      "vicqasfdkxsuyuzspjqunxpyfuhlxfhgfqnlcpdfivqnxqoothnfsbuykfguftgulgldnkkzufssbae",
+                      "九ソミせボぜゾボёaをぜЯまゾタぜタひ縷ダんaバたゼソ",
+                      "ぽマタぁぁ黑ソゼミゼ匚zソダマぁァゾぽミaタゾ弌ミゼタそzぺポせ裹バポハハヲぺチあマ匚ミ",
+                      "hssiißuamtctgqhglmusexyikhcsqctusonubxorssyizhyqpbtbdßjnelxqttkhdalabibuqhiubtßsptrmzelud",
+                      "gbjssllxzzxkmßppyyrhgmoeßizlcmsuqqnvjßudszevtfunflqzqcuubukypßqjcix"
+                     ]
+               }
+           },
+           { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+               expected: {
+                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                   "@odata.type": "#DataJS.Tests.V4.Food",
+                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                   ID: 0,
+                   Name: "Bread",
+                   Description: "Whole grain bread",
+                   ReleaseDate: "1992-01-01T00:00:00Z",
+                   DiscontinuedDate: null,
+                   Rating: 4,
+                   Price: 2.5
+               },
+               data: {
+                   "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                   "@odata.id": "Foods(4)",
+                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                   "@odata.editLink": "Foods(0)",
+                   "@odata.type": "#DataJS.Tests.V4.Food",
+                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                   ID: 0,
+                   Name: "Bread",
+                   Description: "Whole grain bread",
+                   ReleaseDate: "1992-01-01T00:00:00Z",
+                   DiscontinuedDate: null,
+                   Rating: 4,
+                   Price: 2.5
+               }
+           },
+           { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+               expected: {
+                   value : [{
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                       ID: 0,
+                       ComplexInLayerOne:
+                       {
+                           "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                           "@odata.type": "#DataJS.Tests.V4.Food",
+                           "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer1",
+                           "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer1",
+                           "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer1",
+                           "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer1",
+                           ID: 1,
+                           ComplexInLayerTwo:
+                           {
+                               "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                               "@odata.type": "#DataJS.Tests.V4.Food",
+                               "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer2",
+                               "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer2",
+                               "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer2",
+                               "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer2",
+                               ID: 2,
+                               ComplexInLayerThreeList: [
+                               {
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               },
+                               {
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               }],
+                               Name: "BreadInLayer2",
+                               Description: "Whole grain bread inLayer2",
+                               ReleaseDate: "1992-01-01T00:00:00Z",
+                               DiscontinuedDate: null,
+                               Rating: 6,
+                               Price: 4.5
+                           },
+                           Name: ["BreadInLayer1", "BreadInLayer12", "BreadInLayer13"],
+                           Description: "Whole grain bread inLayer1",
+                           ReleaseDate: "1992-01-01T00:00:00Z",
+                           DiscontinuedDate: null,
+                           Rating: 5,
+                           Price: 3.5
+                       },
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 4,
+                       Price: 2.5
+                   },
+                   {
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                       ID: 0,
+                       ComplexInLayerOne:
+                       {
+                           "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                           "@odata.type": "#DataJS.Tests.V4.Food",
+                           "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer1",
+                           "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer1",
+                           "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer1",
+                           "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer1",
+                           ID: 1,
+                           ComplexInLayerTwo:
+                           {
+                               "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                               "@odata.type": "#DataJS.Tests.V4.Food",
+                               "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer2",
+                               "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer2",
+                               "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer2",
+                               "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer2",
+                               ID: 2,
+                               ComplexInLayerThreeList: [
+                               {
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               },
+                               {
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               }],
+                               Name: "BreadInLayer2",
+                               Description: "Whole grain bread inLayer2",
+                               ReleaseDate: "1992-01-01T00:00:00Z",
+                               DiscontinuedDate: null,
+                               Rating: 6,
+                               Price: 4.5
+                           },
+                           Name: ["BreadInLayer1", "BreadInLayer12", "BreadInLayer13"],
+                           Description: "Whole grain bread inLayer1",
+                           ReleaseDate: "1992-01-01T00:00:00Z",
+                           DiscontinuedDate: null,
+                           Rating: 5,
+                           Price: 3.5
+                       },
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 4,
+                       Price: 2.5
+                   }]
+               },
+               data: {
+                   "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                   "@odata.id": "Foods(4)",
+                   "@odata.editLink": "Foods(0)",
+                   value : [{
+                       "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                       "@odata.id": "Foods(4)",
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.editLink": "Foods(0)",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                       ID: 0,
+                       ComplexInLayerOne:
+                       {
+                           "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                           "@odata.id": "Foods(4)",
+                           "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                           "@odata.editLink": "Foods(0)",
+                           "@odata.type": "#DataJS.Tests.V4.Food",
+                           "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer1",
+                           "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer1",
+                           "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer1",
+                           "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer1",
+                           ID: 1,
+                           ComplexInLayerTwo:
+                           {
+                               "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                               "@odata.id": "Foods(4)",
+                               "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                               "@odata.editLink": "Foods(0)",
+                               "@odata.type": "#DataJS.Tests.V4.Food",
+                               "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer2",
+                               "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer2",
+                               "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer2",
+                               "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer2",
+                               ID: 2,
+                               ComplexInLayerThreeList: [
+                               {
+                                   "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                                   "@odata.id": "Foods(4)",
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.editLink": "Foods(0)",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               },
+                               {
+                                   "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                                   "@odata.id": "Foods(4)",
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.editLink": "Foods(0)",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               }],
+                               Name: "BreadInLayer2",
+                               Description: "Whole grain bread inLayer2",
+                               ReleaseDate: "1992-01-01T00:00:00Z",
+                               DiscontinuedDate: null,
+                               Rating: 6,
+                               Price: 4.5
+                           },
+                           Name: ["BreadInLayer1", "BreadInLayer12", "BreadInLayer13"],
+                           Description: "Whole grain bread inLayer1",
+                           ReleaseDate: "1992-01-01T00:00:00Z",
+                           DiscontinuedDate: null,
+                           Rating: 5,
+                           Price: 3.5
+                       },
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 4,
+                       Price: 2.5
+                   },
+                   {
+                       "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                       "@odata.id": "Foods(4)",
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.editLink": "Foods(0)",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                       ID: 0,
+                       ComplexInLayerOne:
+                       {
+                           "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                           "@odata.id": "Foods(4)",
+                           "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                           "@odata.editLink": "Foods(0)",
+                           "@odata.type": "#DataJS.Tests.V4.Food",
+                           "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer1",
+                           "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer1",
+                           "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer1",
+                           "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer1",
+                           ID: 1,
+                           ComplexInLayerTwo:
+                           {
+                               "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                               "@odata.id": "Foods(4)",
+                               "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                               "@odata.editLink": "Foods(0)",
+                               "@odata.type": "#DataJS.Tests.V4.Food",
+                               "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer2",
+                               "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer2",
+                               "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer2",
+                               "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer2",
+                               ID: 2,
+                               ComplexInLayerThreeList: [
+                               {
+                                   "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                                   "@odata.id": "Foods(4)",
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.editLink": "Foods(0)",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               },
+                               {
+                                   "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                                   "@odata.id": "Foods(4)",
+                                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                                   "@odata.editLink": "Foods(0)",
+                                   "@odata.type": "#DataJS.Tests.V4.Food",
+                                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                                   ID: 3,
+                                   Name: "BreadInLayer3",
+                                   Description: "Whole grain bread inLayer3",
+                                   ReleaseDate: "1992-01-01T00:00:00Z",
+                                   DiscontinuedDate: null,
+                                   Rating: 7,
+                                   Price: 5.5
+                               }],
+                               Name: "BreadInLayer2",
+                               Description: "Whole grain bread inLayer2",
+                               ReleaseDate: "1992-01-01T00:00:00Z",
+                               DiscontinuedDate: null,
+                               Rating: 6,
+                               Price: 4.5
+                           },
+                           Name: ["BreadInLayer1", "BreadInLayer12", "BreadInLayer13"],
+                           Description: "Whole grain bread inLayer1",
+                           ReleaseDate: "1992-01-01T00:00:00Z",
+                           DiscontinuedDate: null,
+                           Rating: 5,
+                           Price: 3.5
+                       },
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 4,
+                       Price: 2.5
+                   }]
+               }
+           },
+           { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+               expected: {
+                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                   "@odata.type": "#DataJS.Tests.V4.Food",
+                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                   ID: 0,
+                   ComplexInLayerOne:
+                   {
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer1",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer1",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer1",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer1",
+                       ID: 1,
+                       ComplexInLayerTwo:
+                       {
+                           "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                           "@odata.type": "#DataJS.Tests.V4.Food",
+                           "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer2",
+                           "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer2",
+                           "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer2",
+                           "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer2",
+                           ID: 2,
+                           ComplexInLayerThree:
+                           {
+                               "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                               "@odata.type": "#DataJS.Tests.V4.Food",
+                               "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                               "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                               "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                               "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                               ID: 3,
+                               Name: "BreadInLayer3",
+                               Description: "Whole grain bread inLayer3",
+                               ReleaseDate: "1992-01-01T00:00:00Z",
+                               DiscontinuedDate: null,
+                               Rating: 7,
+                               Price: 5.5
+                           },
+                           Name: "BreadInLayer2",
+                           Description: "Whole grain bread inLayer2",
+                           ReleaseDate: "1992-01-01T00:00:00Z",
+                           DiscontinuedDate: null,
+                           Rating: 6,
+                           Price: 4.5
+                       },
+                       Name: "BreadInLayer1",
+                       Description: "Whole grain bread inLayer1",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 5,
+                       Price: 3.5
+                   },
+                   Name: "Bread",
+                   Description: "Whole grain bread",
+                   ReleaseDate: "1992-01-01T00:00:00Z",
+                   DiscontinuedDate: null,
+                   Rating: 4,
+                   Price: 2.5
+               },
+               data: {
+                   "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                   "@odata.id": "Foods(4)",
+                   "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                   "@odata.editLink": "Foods(0)",
+                   "@odata.type": "#DataJS.Tests.V4.Food",
+                   "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                   "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                   "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                   "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                   ID: 0,
+                   ComplexInLayerOne:
+                   {
+                       "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                       "@odata.id": "Foods(4)",
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.editLink": "Foods(0)",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer1",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer1",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer1",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer1",
+                       ID: 1,
+                       ComplexInLayerTwo:
+                       {
+                           "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                           "@odata.id": "Foods(4)",
+                           "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                           "@odata.editLink": "Foods(0)",
+                           "@odata.type": "#DataJS.Tests.V4.Food",
+                           "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer2",
+                           "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer2",
+                           "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer2",
+                           "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer2",
+                           ID: 2,
+                           ComplexInLayerThree:
+                           {
+                               "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                               "@odata.id": "Foods(4)",
+                               "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                               "@odata.editLink": "Foods(0)",
+                               "@odata.type": "#DataJS.Tests.V4.Food",
+                               "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink/layer3",
+                               "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink/layer3",
+                               "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType/layer3",
+                               "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag/layer3",
+                               ID: 3,
+                               Name: "BreadInLayer3",
+                               Description: "Whole grain bread inLayer3",
+                               ReleaseDate: "1992-01-01T00:00:00Z",
+                               DiscontinuedDate: null,
+                               Rating: 7,
+                               Price: 5.5
+                           },
+                           Name: "BreadInLayer2",
+                           Description: "Whole grain bread inLayer2",
+                           ReleaseDate: "1992-01-01T00:00:00Z",
+                           DiscontinuedDate: null,
+                           Rating: 6,
+                           Price: 4.5
+                       },
+                       Name: "BreadInLayer1",
+                       Description: "Whole grain bread inLayer1",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 5,
+                       Price: 3.5
+                   },
+                   Name: "Bread",
+                   Description: "Whole grain bread",
+                   ReleaseDate: "1992-01-01T00:00:00Z",
+                   DiscontinuedDate: null,
+                   Rating: 4,
+                   Price: 2.5
+               }
+           },
+           { context: { response: { requestUri: "http://base.org" }, dataServiceVersion: "4.0" },
+               expected: {
+                   value: [{
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                       ID: 0,
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 4,
+                       Price: 2.5
+                   },
+                   {
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink2",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink2",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType2",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag2",
+                       ID: 1,
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1999-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 6,
+                       Price: 3.5
+                   }]
+               },
+               data: {
+                   value: [{
+                       "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                       "@odata.id": "Foods(4)",
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.editLink": "Foods(0)",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag",
+                       ID: 0,
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1992-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 4,
+                       Price: 2.5
+                   },
+                   {
+                       "@odata.context": "http://base.org/$metadata#Foods/$entity",
+                       "@odata.id": "Foods(2)",
+                       "@odata.etag": "W/MjAxMy0wNS0yN1QxMTo1OFo=",
+                       "@odata.editLink": "Foods(2)",
+                       "@odata.type": "#DataJS.Tests.V4.Food",
+                       "@odata.mediaEditLink": "http://base.org/$metadata#Foods/mediaEditLink2",
+                       "@odata.mediaReadLink": "http://base.org/$metadata#Foods/mediaReadLink2",
+                       "@odata.mediaContentType": "http://base.org/$metadata#Foods/mediaContentType2",
+                       "@odata.mediaEtag": "http://base.org/$metadata#Foods/mediaEtag2",
+                       ID: 1,
+                       Name: "Bread",
+                       Description: "Whole grain bread",
+                       ReleaseDate: "1999-01-01T00:00:00Z",
+                       DiscontinuedDate: null,
+                       Rating: 6,
+                       Price: 3.5
+                   }]
+               }
+           }
+          ];
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var data = tests[i].data ? tests[i].data : tests[i].expected;
+            var actual = OData.jsonSerializer(OData.jsonHandler, data, tests[i].context);
+            var expected = window.JSON.stringify(tests[i].expected);
+            djstest.assertAreEqualDeep(actual, expected, "test " + i + "didn't return the expected data");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function normalizeHeadersReadTest() {
+        // Verifies that headers are normalized for reading.
+        // See issue at http://datajs.codeplex.com/workitem/148
+        window.MockHttpClient.clear().addResponse("/foo", {
+            statusCode: 200,
+            body: { "@odata.context": "http://foo", value: [] },
+            headers: { "unknown": "u", "Content-Encoding": "compress, gzip", "Content-Length": "8042",
+                "Content-Type": "application/json", "OData-Version": "4.0", "Etag": "Vetag", "Location": "foo", "OData-EntityId": "entityId",
+                "Preference-Applied": "prefered", "Retry-After": "retry"
+            }
+        });
+
+        OData.read("/foo", function (data, response) {
+            // djstest.assertAreEqual(data.results.length, 2, "data.results.length has two entries");
+            djstest.assertAreEqual(response.headers.unknown, "u", "u unmodified");
+            djstest.assertAreEqual(response.headers["Content-Encoding"], "compress, gzip", "Content-Encoding available");
+            djstest.assertAreEqual(response.headers["Content-Length"], "8042", "Content-Length available");
+            djstest.assertAreEqual(response.headers["Content-Type"], "application/json", "Content-Type available");
+            djstest.assertAreEqual(response.headers["ETag"], "Vetag", "Content-Type available");
+            djstest.assertAreEqual(response.headers["Location"], "foo", "Content-Type available");
+            djstest.assertAreEqual(response.headers["OData-EntityId"], "entityId", "OData-EntityId available");
+            djstest.assertAreEqual(response.headers["Preference-Applied"], "prefered", "Preference-Applied available");
+            djstest.assertAreEqual(response.headers["Retry-After"], "retry", "Retry available");
+            djstest.assertAreEqual(response.headers["OData-Version"], "4.0", "OData-Version available");
+            djstest.done();
+        }, undefined, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function normalizeHeadersWriteTest() {
+        // Verifies that headers are normalized for writing.
+        // See issue at http://datajs.codeplex.com/workitem/148
+        window.MockHttpClient.clear().addRequestVerifier("/foo", function (request) {
+            djstest.assertAreEqual(request.headers.Accept, "application/json", "Accept available");
+            djstest.assertAreEqual(request.headers["Content-Type"], "application/json", "json found");
+            djstest.assertAreEqual(request.headers["Content-Encoding"], "compress, gzip", "Content-Encoding available");
+            djstest.assertAreEqual(request.headers["Content-Length"], "8042", "Content-Length available");
+            djstest.assertAreEqual(request.headers["OData-Version"], "4.0", "OData-Version available");
+            djstest.assertAreEqual(request.headers["Accept-Charset"], "Accept-Charset", "Accept-Charset available");
+            djstest.assertAreEqual(request.headers["If-Match"], "true", "If-Match available");
+            djstest.assertAreEqual(request.headers["If-None-Match"], "false", "If-None-Match available");
+            djstest.assertAreEqual(request.headers["OData-Isolation"], "isolation", "OData-Isolation available");
+            djstest.assertAreEqual(request.headers["OData-MaxVersion"], "4.0", "OData-MaxVersion available");
+            djstest.assertAreEqual(request.headers["Prefer"], "prefer", "prefer available");
+            djstest.done();
+        });
+
+        var request = {
+            method: "POST",
+            requestUri: "/foo",
+            data: { value: 123 },
+            headers: { "Accept": "application/json", "Content-Encoding": "compress, gzip", "Content-Length": "8042", "content-type": "application/json", "OData-Version": "4.0",
+                "accept-charset": "Accept-Charset", "if-match": "true", "if-none-match": "false", "odata-isolation": "isolation",
+                "odata-maxversion": "4.0", "prefer": "prefer"
+            }
+        };
+        OData.request(request, function (data) {
+        }, undefined, undefined, MockHttpClient);
+    });
+
+    // DATAJS INTERNAL END
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-links-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-links-functional-tests.html b/JSLib/tests/odata-links-functional-tests.html
new file mode 100644
index 0000000..95b0d36
--- /dev/null
+++ b/JSLib/tests/odata-links-functional-tests.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>OData tests against local service</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/ODataReadOracle.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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="odata-links-functional-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">OData.Read tests against local in-memory service</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/48761e07/JSLib/tests/odata-links-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-links-functional-tests.js b/JSLib/tests/odata-links-functional-tests.js
new file mode 100644
index 0000000..6ae12ea
--- /dev/null
+++ b/JSLib/tests/odata-links-functional-tests.js
@@ -0,0 +1,232 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    var uriRegEx = /^([^:/?#]+:)?(\/\/[^/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/;
+    var uriPartNames = ["scheme", "authority", "path", "query", "fragment"];
+
+    var getURIInfo = function (uri) {
+        /// <summary>Gets information about the components of the specified URI.</summary>
+        /// <param name="uri" type="String">URI to get information from.</param>
+        /// <returns type="Object">
+        /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available.
+        /// </returns>
+
+        var result = { isAbsolute: false };
+
+        if (uri) {
+            var matches = uriRegEx.exec(uri);
+            if (matches) {
+                var i, len;
+                for (i = 0, len = uriPartNames.length; i < len; i++) {
+                    if (matches[i + 1]) {
+                        result[uriPartNames[i]] = matches[i + 1];
+                    }
+                }
+            }
+            if (result.scheme) {
+                result.isAbsolute = true;
+            }
+        }
+
+        return result;
+    };
+
+    var normalizeURI = function (uri, base) {
+        /// <summary>Normalizes a possibly relative URI with a base URI.</summary>
+        /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
+        /// <param name="base" type="String" mayBeNull="true">Base URI to compose with.</param>
+        /// <returns type="String">The composed URI if relative; the original one if absolute.</returns>
+
+        if (!base) {
+            return uri;
+        }
+
+        var uriInfo = getURIInfo(uri);
+        if (uriInfo.isAbsolute) {
+            return uri;
+        }
+
+        var baseInfo = getURIInfo(base);
+        var normInfo = {};
+
+        if (uriInfo.authority) {
+            normInfo.authority = uriInfo.authority;
+            normInfo.path = uriInfo.path;
+            normInfo.query = uriInfo.query;
+        } else {
+            if (!uriInfo.path) {
+                normInfo.path = baseInfo.path;
+                normInfo.query = uriInfo.query || baseInfo.query;
+            } else {
+                if (uriInfo.path.charAt(0) === '/') {
+                    normInfo.path = uriInfo.path;
+                } else {
+                    normInfo.path = mergeUriPathWithBase(uriInfo, baseInfo);
+                }
+
+                normInfo.query = uriInfo.query;
+            }
+
+            normInfo.authority = baseInfo.authority;
+        }
+
+        normInfo.scheme = baseInfo.scheme;
+        normInfo.fragment = uriInfo.fragment;
+
+        return "".concat(
+            normInfo.scheme || "",
+            normInfo.authority || "",
+            normInfo.path || "",
+            normInfo.query || "",
+            normInfo.fragment || "");
+    };
+
+    var mergeUriPathWithBase = function (uriInfo, baseInfo) {
+        /// <summary>Merges the path of a relative URI and a base URI.</summary>
+        /// <param name="uriInfo">URI component information for the relative URI.</param>
+        /// <param name="baseInfo">URI component information for the base URI.</param>
+        /// <returns type="String">A string with the merged path.</returns>
+
+        var basePath = "/";
+        if (baseInfo.path) {
+            var end = baseInfo.path.lastIndexOf("/");
+            basePath = baseInfo.path.substring(0, end);
+
+            if (basePath.charAt(basePath.length - 1) !== "/") {
+                basePath = basePath + "/";
+            }
+        }
+
+        return basePath + uriInfo.path;
+    };
+
+    var services = [
+        "./endpoints/FoodStoreDataServiceV4.svc"
+    ];
+
+    var mimeTypes = [undefined, "application/json;odata.metadata=minimal"/*, "application/xml"*/];
+
+    var httpStatusCode = {
+        created: 201,
+        noContent: 204,
+        notFound: 404
+    };
+
+    $.each(services, function (_, service) {
+
+        var foodsFeed = service + "/Foods";
+        var categoriesFeed = service + "/Categories";
+
+        var baseLinkUri = normalizeURI(service.substr(2), window.location.href);
+
+        var newFoodLinks = {
+            "@odata.id": baseLinkUri + "/Foods" + "(1)"
+        };
+
+        var newCategoryLinks = {
+            "@odata.id": baseLinkUri + "/Categories" + "(2)"
+        };
+
+
+        module("Functional", {
+            setup: function () {
+                djstest.wait(function (done) {
+                    $.post(service + "/ResetData", done);
+                });
+            }
+        });
+
+        var readLinksFeed = categoriesFeed + "(1)/Foods/$ref";
+        var readLinksEntry = foodsFeed + "(0)/Category/$ref";
+
+        $.each(mimeTypes, function (_, mimeType) {
+
+            var headers = mimeType ? { "Content-Type": mimeType, Accept: mimeType} : undefined;
+
+            djstest.addTest(function readValidLinksFeedTests(params) {
+                djstest.assertsExpected(1);
+                OData.read({ requestUri: params.linksFeed, headers: headers },
+                    function (data, response) {
+                        window.ODataReadOracle.readLinksFeed(params.linksFeed,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, params.mimeType
+                        );
+                    },
+                    unexpectedErrorHandler
+                );
+            }, "Testing valid read of " + readLinksFeed + " with " + mimeType, { linksFeed: readLinksFeed, mimeType: mimeType });
+
+            djstest.addTest(function readValidLinksEntryTest(params) {
+                djstest.assertsExpected(1);
+                OData.read({ requestUri: params.linksEntry, headers: headers },
+                    function (data, response) {
+                        window.ODataReadOracle.readLinksEntry(params.linksEntry,
+                            function (expectedData) {
+                                djstest.assertAreEqualDeep(data, expectedData, "Response data not same as expected");
+                                djstest.done();
+                            }, params.mimeType
+                        );
+                    },
+                    unexpectedErrorHandler
+                );
+            }, "Testing valid read of " + readLinksEntry + " with " + mimeType, { linksEntry: readLinksEntry, mimeType: mimeType });
+
+            djstest.addTest(function addLinksEntityTest(mimeType) {
+
+                var request = {
+                    requestUri: foodsFeed + "(1)/Category/$ref",
+                    method: "PUT",
+                    headers: djstest.clone(headers),
+                    data: newCategoryLinks
+                };
+
+
+                OData.request(request, function (data, response) {
+                    var httpOperation = request.method + " " + request.requestUri;
+                    djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code: " + httpOperation);
+                    ODataReadOracle.readLinksEntry(request.requestUri, function (actualData) {
+                        if (actualData && actualData["@odata.context"]) {
+                            delete actualData["@odata.context"];
+                        }
+                        
+                        djstest.assertAreEqualDeep(actualData, request.data, "Verify new links entry against the request: " + httpOperation);
+                        djstest.done();
+                    });
+                }, unexpectedErrorHandler);
+
+            }, "Add new links entity (mimeType = " + mimeType + " service = " + service + ")", mimeType);
+
+            djstest.addTest(function addLinksFeedTest(mimeType) {
+
+                var request = {
+                    requestUri: categoriesFeed + "(2)/Foods/$ref",
+                    method: "POST",
+                    headers: djstest.clone(headers),
+                    data: newFoodLinks
+                };
+
+                OData.request(request, function (data, response) {
+
+                    var httpOperation = request.method + " " + request.requestUri;
+                    djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code: " + httpOperation);
+
+                    OData.read(request.requestUri, function (data, response) {
+                        ODataReadOracle.readLinksFeed(request.requestUri, function (actualData) {
+                            djstest.assertAreEqualDeep(actualData, response.data, "Verify updated links entry against the request: " + httpOperation);
+                            djstest.done();
+                        });
+                    });
+                }, unexpectedErrorHandler);
+            }, "Update entity (mimeType = " + mimeType + " service = " + service + ")", mimeType);
+        });
+    });
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-metadata-awareness-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-metadata-awareness-functional-tests.html b/JSLib/tests/odata-metadata-awareness-functional-tests.html
new file mode 100644
index 0000000..75fb2f2
--- /dev/null
+++ b/JSLib/tests/odata-metadata-awareness-functional-tests.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>metadata awareness 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/ODataReadOracle.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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="odata-metadata-awareness-functional-tests.js"></script>
+</head>
+<body>
+    <h1 id="qunit-header">metadata awareness tests</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-metadata-awareness-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-metadata-awareness-functional-tests.js b/JSLib/tests/odata-metadata-awareness-functional-tests.js
new file mode 100644
index 0000000..b86b418
--- /dev/null
+++ b/JSLib/tests/odata-metadata-awareness-functional-tests.js
@@ -0,0 +1,227 @@
+/// <reference path="common/djstest.js" />
+/// <reference path="../src/odata.js" />
+/// <reference path="common/ODataReadOracle.js" />
+
+(function (window, undefined) {
+    var unexpectedErrorHandler = function (err) {
+        djstest.assert(false, "Unexpected call to error handler with error: " + djstest.toString(err));
+        djstest.done();
+    };
+
+    var service = "./endpoints/EpmDataService.svc";
+    var metadataUri = service + "/$metadata";
+
+    var httpStatusCode = {
+        ok: 200,
+        created: 201,
+        noContent: 204
+    };
+
+    var acceptHeaders = { Accept: "application/atom+xml" };
+    var mimeHeaders = { "Content-Type": "application/atom+xml", Accept: "application/atom+xml" };
+    var keepInContentVariations = [true, false];
+    var feedUris = { "true": service + "/ReplicatedEntries", "false": service + "/MappedEntries" };
+    var typeNames = { "true": "DataJS.Tests.ReplicatedEntry", "false": "DataJS.Tests.MappedEntry" };
+    var selectProperties = ["Published", "Author", "CustomElement", "NestedElement1", "Published,Author,CustomElement,NestedElement1"];
+
+    var newEntry = {
+        UnmappedField: "Unmapped100",
+        Author: {
+            Email: "AuthorEmail100",
+            Name: "AuthorName100",
+            Uri: "http://www.example.com/AuthorUri100",
+            Contributor: {
+                Email: "ContributorEmail100",
+                Name: "ContributorName100",
+                Uri: "http://www.example.com/ContributorUri1000"
+            }
+        },
+        Published: "2100-01-01T00:00:00-08:00",
+        Rights: "Rights100",
+        Summary: "<xmlElement xmlns=\"http://www.example.com/dummy\" attr=\"value100\">Summary100</xmlElement>",
+        Title: "Title<b>100</b>",
+        Updated: "2100-01-01T00:00:00-08:00",
+        CustomElement: "CustomElement100",
+        CustomAttribute: "CustomAttribute100",
+        NestedElement1: "NestedElement1_100",
+        NestedElement2: "NestedElement2_100",
+        CommonAttribute1: "CommonAttribute1_100",
+        CommonAttribute2: "CommonAttribute2_100",
+        Location: {
+            Lat: 1.23,
+            Long: 4.56
+        }
+    };
+
+    var newSpecialValuesEntry = $.extend(true, {}, newEntry, {
+        Author: {
+            Email: null,
+            Name: "",
+            Uri: " ",
+            Contributor: {
+                Email: null,
+                Name: "",
+                Uri: " "
+            }
+        },
+        Rights: null,
+        Summary: "",
+        Title: " ",
+        CustomElement: null,
+        NestedElement1: "",
+        NestedElement2: " ",
+        CustomAttribute: null,
+        CommonAttribute1: "",
+        CommonAttribute2: " "
+    });
+
+    var nullComplexTypeEntry = $.extend(true, {}, newEntry, {
+        Author: { Contributor: null },
+        Location: null
+    });
+
+    var testEntries = [
+        { data: newEntry, description: "entry" },
+        { data: newSpecialValuesEntry, description: "entry containing null and empty string" },
+        { data: nullComplexTypeEntry, description: "entry containing null complex type value" }
+    ];
+
+    var serviceMetadata;
+    var getMetadata = function (callback) {
+        /// <summary>Common function for tests to get and cache metadata, to reduce network calls made by tests</summary>
+        if (!serviceMetadata) {
+            OData.read(metadataUri, function (metadata) {
+                serviceMetadata = metadata;
+                callback(metadata);
+            }, unexpectedErrorHandler, OData.metadataHandler);
+        }
+        else {
+            callback(serviceMetadata);
+        }
+    }
+
+    module("Functional", {
+        setup: function () {
+            djstest.wait(function (done) {
+                $.post(service + "/ResetData", done);
+            });
+            OData.defaultMetadata = [];
+            OData.jsonHandler.recognizeDates = false;
+        }
+    });
+
+    $.each(selectProperties, function (_, selectProperty) {
+        djstest.addTest(function getSelectPropertiesOnEntry(propertyToSelect) {
+            var entryUri = feedUris["true"] + "(0)?$select=" + propertyToSelect;
+            djstest.assertsExpected(2);
+            getMetadata(function (metadata) {
+                OData.defaultMetadata.push(metadata);
+                OData.read({ requestUri: entryUri, headers: acceptHeaders }, function (data, response) {
+                    djstest.assertAreEqual(response.statusCode, httpStatusCode.ok, "Verify response code");
+                    ODataReadOracle.readJson(entryUri, function (expectedData) {
+                        djstest.assertWithoutMetadata(data, expectedData, "Verify data");
+                        djstest.done();
+                    })
+                }, unexpectedErrorHandler);
+            }, unexpectedErrorHandler, OData.metadataHandler);
+        }, "GET with mapped properties selecting " + selectProperty + " with keepInContent = true", selectProperty);
+    });
+
+    $.each(keepInContentVariations, function (_, keepInContent) {
+        var feedUri = feedUris[keepInContent];
+
+        $.each(testEntries, function (entryIndex, testEntry) {
+            params = {
+                feedUri: feedUri,
+                testEntry: $.extend(true, {}, testEntry, {
+                    data: {
+                        "__metadata": { type: typeNames[keepInContent] }
+                    }
+                })
+            };
+
+            djstest.addTest(function getMappedEntry(params) {
+                var entryUri = params.feedUri + "(" + entryIndex + ")";
+                djstest.assertsExpected(2);
+                getMetadata(function (metadata) {
+                    OData.defaultMetadata.push(metadata);
+                    OData.read({ requestUri: entryUri, headers: acceptHeaders }, function (data, response) {
+                        djstest.assertAreEqual(response.statusCode, httpStatusCode.ok, "Verify response code");
+                        ODataReadOracle.readJson(entryUri, function (expectedData) {
+                            djstest.assertWithoutMetadata(data, expectedData, "Verify data");
+                            djstest.done();
+                        })
+                    }, unexpectedErrorHandler);
+                }, unexpectedErrorHandler, OData.metadataHandler);
+            }, "GET " + params.testEntry.description + " with mapped properties: keepInContent = " + keepInContent, params);
+
+            djstest.addTest(function postMappedEntry(params) {
+                var postEntry = $.extend(true, {}, params.testEntry.data, { ID: 100 });
+                djstest.assertsExpected(2);
+                getMetadata(function (metadata) {
+                    OData.request({ requestUri: params.feedUri, method: "POST", headers: djstest.clone(mimeHeaders), data: postEntry }, function (data, response) {
+                        djstest.assertAreEqual(response.statusCode, httpStatusCode.created, "Verify response code");
+                        ODataReadOracle.readJson(feedUri + "(" + postEntry.ID + ")", function (actualData) {
+                            djstest.assertWithoutMetadata(actualData, postEntry, "Verify new entry data against server");
+                            djstest.done();
+                        })
+                    }, unexpectedErrorHandler, undefined, undefined, metadata);
+                }, unexpectedErrorHandler, OData.metadataHandler);
+            }, "POST " + params.testEntry.description + " with mapped properties: keepInContent = " + keepInContent, params);
+
+            djstest.addTest(function putMappedEntry(params) {
+                var entryUri = params.feedUri + "(0)";
+                djstest.assertsExpected(2);
+                getMetadata(function (metadata) {
+                    OData.defaultMetadata.push(metadata);
+                    OData.request({ requestUri: entryUri, method: "PUT", headers: djstest.clone(mimeHeaders), data: params.testEntry.data }, function (data, response) {
+                        djstest.assertAreEqual(response.statusCode, httpStatusCode.noContent, "Verify response code");
+                        ODataReadOracle.readJson(entryUri, function (actualData) {
+                            djstest.assertWithoutMetadata(actualData, $.extend({ ID: 0 }, params.testEntry.data), "Verify updated entry data against server");
+                            djstest.done();
+                        })
+                    }, unexpectedErrorHandler);
+                }, unexpectedErrorHandler, OData.metadataHandler);
+            }, "PUT " + params.testEntry.description + " with mapped properties: keepInContent = " + keepInContent, params);
+        });
+    });
+
+    var descriptions = ["base type", "derived type"];
+    $.each(descriptions, function (index, _) {
+        djstest.addTest(function getHierarchicalEntry(index) {
+            var entryUri = service + "/HierarchicalEntries(" + index + ")";
+            djstest.assertsExpected(2);
+            getMetadata(function (metadata) {
+                OData.read({ requestUri: entryUri, headers: acceptHeaders }, function (data, response) {
+                    djstest.assertAreEqual(response.statusCode, httpStatusCode.ok, "Verify response code");
+                    ODataReadOracle.readJson(entryUri, function (expectedData) {
+                        djstest.assertWithoutMetadata(data, expectedData, "Verify data");
+                        djstest.done();
+                    })
+                }, unexpectedErrorHandler, undefined, undefined, metadata);
+            }, unexpectedErrorHandler, OData.metadataHandler);
+        }, "GET " + descriptions[index] + " with mapped properties: keepInContent = false", index);
+    });
+
+    $.each([false, true], function (_, recognizeDates) {
+        djstest.addTest(function readDateTimeWithMetadataTest(params) {
+            var foodStoreDataService = "./endpoints/FoodStoreDataServiceV4.svc";
+            var specialDaysEndpoint = foodStoreDataService + "/SpecialDays";
+
+            djstest.assertsExpected(1);
+            OData.jsonHandler.recognizeDates = params.recognizeDates;
+            OData.read(foodStoreDataService + "/$metadata", function (metadata) {
+                OData.read({ requestUri: specialDaysEndpoint, headers: { Accept: params.accept} }, function (data, response) {
+                    // Because our oracle isn't metadata aware, it is not 100% correct, so we will pass in recognizeDates = true
+                    // in all cases and manually fix up the property that was incorrectly converted
+                    window.ODataReadOracle.readFeed(specialDaysEndpoint, function (expectedData) {
+                        // Fix up the string property that has a "date-like" string deliberately injected
+                        expectedData.results[2].Name = "/Date(" + expectedData.results[2].Name.valueOf() + ")/";
+                        djstest.assertAreEqualDeep(data, expectedData, "Verify response data");
+                        djstest.done();
+                    }, params.accept, true);
+                }, unexpectedErrorHandler, undefined, undefined, metadata);
+            }, unexpectedErrorHandler, OData.metadataHandler);
+        }, "GET metadata-aware JSON dates with recognizeDates=" + recognizeDates, { recognizeDates: recognizeDates, accept: "application/json;odata.metadata=minimal" });
+    });
+})(this);


[07/10] [OLINGO-276] Check in the missing test source codes.

Posted by bi...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-cache-fperf-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-fperf-tests.html b/JSLib/tests/odata-cache-fperf-tests.html
new file mode 100644
index 0000000..3323199
--- /dev/null
+++ b/JSLib/tests/odata-cache-fperf-tests.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>datajs.cache functional perf tests</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+    <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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+    
+    <script type="text/javascript" src="common/CacheOracle.js"></script>  
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="odata-cache-fperf-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">datajs.cache functional perf tests</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-cache-fperf-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-fperf-tests.js b/JSLib/tests/odata-cache-fperf-tests.js
new file mode 100644
index 0000000..441022c
--- /dev/null
+++ b/JSLib/tests/odata-cache-fperf-tests.js
@@ -0,0 +1,103 @@
+/// <reference path="../src/datajs.js" />
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/cache.js" />
+/// <reference path="common/CacheOracle.js" />
+/// <reference path="common/djstest.js" />
+
+(function (window, undefined) {
+
+    var slowHttpClient = {
+        latency: 750,
+        provider: OData.defaultHttpClient,
+        request: function (request, success, error) {
+            setTimeout(function () {
+                slowHttpClient.provider.request(request, success, error);
+            }, slowHttpClient.latency);
+        }
+    };
+
+    var feeds = [
+        { uri: "./endpoints/FoodStoreDataServiceV4.svc/Foods" }
+    ];
+
+    module("Functional", {
+        setup: function () {
+            OData.defaultHttpClient = slowHttpClient;
+        },
+        teardown: function () {
+            OData.defaultHttpClient = slowHttpClient.provider;
+        }
+    });
+
+    var cacheReadRangeWallClockTest = function (totalReads, interval, mechanism, source, pageSize, prefetchSize, generateRange, threshold) {
+        /// <summary>Cache readRange wall-clock test</summary>
+        /// <remarks>
+        /// The average time computed by the wall-clock test does *not* include the initial readRange
+        /// </remarks>
+        /// <param name="totalReads">Number of reads to collect data from</param>
+        /// <param name="interval">Interval (milliseconds) between reads</param>
+        /// <param name="mechanism">The cache store mechanism</param>
+        /// <param name="source">The feed source</param>
+        /// <param name="pageSize">The page size</param>
+        /// <param name="prefetchSize">The prefetch size</param>
+        /// <param name="generateRange">The range generator function: given the read index, returns the readRange index and count</param>
+        /// <param name="threshold">The average read time threshold for test to pass; if not specified, defaults to the slowHttpClient latency</param>
+        /// <returns>The test function</param>
+        return function () {
+            var cache = datajs.createDataCache({ name: "cache" + new Date().valueOf(), source: source, pageSize: pageSize, prefetchSize: prefetchSize });
+            var totalTime = 0;
+            var readCount = 0;
+
+            var callReadRange = function () {
+                var range = generateRange(readCount);
+                var startTime = new Date().valueOf();
+                cache.readRange(range.index, range.count).then(function (data) {
+                    var duration = (new Date().valueOf()) - startTime;
+                    djstest.log("readRange " + readCount + " [" + range.index + ", " + range.count + "]: " + duration + "ms");
+
+                    // The first readRange is not counted
+                    totalTime += (readCount > 0) ? duration : 0;
+                    readCount += 1;
+
+                    if (readCount < totalReads) {
+                        setTimeout(callReadRange, interval);
+                    } else {
+                        // The first readRange is not counted
+                        var averageTime = totalTime / (totalReads - 1);
+                        var actualThreshold = threshold === undefined ? slowHttpClient.latency : threshold;
+                        djstest.assert(averageTime < actualThreshold, "Average: " + averageTime + "ms, Threshold: " + actualThreshold + "ms");
+                        djstest.destroyCacheAndDone(cache);
+                    }
+                }, function (err) {
+                    djstest.fail("Unexpected call to error handler with error: " + djstest.toString(err));
+                    djstest.destroyCacheAndDone(cache);
+                });
+            };
+
+            callReadRange();
+        };
+    };
+
+    $.each(CacheOracle.mechanisms, function (_, mechanism) {
+        if (mechanism !== "best" && CacheOracle.isMechanismAvailable(mechanism)) {
+            $.each(feeds, function (_, feed) {
+                djstest.addTest(cacheReadRangeWallClockTest(2, 1000, mechanism, feed.uri, 5, 0, function () {
+                    return { index: 0, count: 5 };
+                }), "Cache small single-page wall-clock test with " + mechanism + " on " + feed.uri);
+
+                djstest.addTest(cacheReadRangeWallClockTest(5, 1000, mechanism, feed.uri, 3, -1, function (readCount) {
+                    return { index: readCount * 3, count: 3 };
+                }), "Cache page-by-page wall-clock test with " + mechanism + " on " + feed.uri);
+
+                djstest.addTest(cacheReadRangeWallClockTest(5, 1000, mechanism, feed.uri, 3, -1, function (readCount) {
+                    return { index: readCount, count: 3 };
+                }), "Cache line-by-line wall-clock test with " + mechanism + " on " + feed.uri);
+            });
+
+            var largeFeedUri = "./endpoints/LargeCollectionService.svc/Customers";
+            djstest.addTest(cacheReadRangeWallClockTest(2, 1000, mechanism, largeFeedUri, 100, 0, function () {
+                return { index: 0, count: 500 };
+            }), "Cache large single-page wall-clock test with " + mechanism + " on " + largeFeedUri, undefined, 60000);
+        }
+    });
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/48761e07/JSLib/tests/odata-cache-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-functional-tests.html b/JSLib/tests/odata-cache-functional-tests.html
new file mode 100644
index 0000000..e6f2992
--- /dev/null
+++ b/JSLib/tests/odata-cache-functional-tests.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>OData tests against local service</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+    <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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+    
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="common/CacheOracle.js"></script>
+    <script type="text/javascript" src="common/ObservableHttpClient.js"></script>
+    <script type="text/javascript" src="common/ODataReadOracle.js"></script>
+    <script type="text/javascript" src="odata-cache-functional-tests.js"></script>  
+</head>
+<body>
+ <h1 id="qunit-header">datajs.cache tests against local in-memory service</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/48761e07/JSLib/tests/odata-cache-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-functional-tests.js b/JSLib/tests/odata-cache-functional-tests.js
new file mode 100644
index 0000000..1a65c6f
--- /dev/null
+++ b/JSLib/tests/odata-cache-functional-tests.js
@@ -0,0 +1,611 @@
+/// <reference path="../src/datajs.js" />
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/cache.js" />
+/// <reference path="common/djstest.js" />
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;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) {
+        /// <summary>Validates the data returned by readRange</summary>
+        /// <param name="cache" type="Object">The cache object</param>
+        /// <param name="data" type="Object">The data returned by the cache</param>
+        /// <param name="source" type="Object">The base URI of the feed, or the custom data source</param>
+        /// <param name="skipValue type="Integer">The skip value</param>
+        /// <param name="takeValue" type="Integer">The take value</param>
+        /// <param name="finished" type="Function">Callback function called after data is verified</param>
+        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) {
+        /// <summary>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.</summary>
+        /// <param name="done" type="Function">Function to be called after DOM storage is cleared.</param>
+        if (window.localStorage) {
+            window.localStorage.clear();
+        }
+        done();
+    };
+
+    var cleanIndexedDb = function (done) {
+        /// <summary>Cleans all the data saved in the browser's IndexedDb Storage.</summary>
+        /// <param name="done" type="Function">Function to be called after indexedDb is cleared.</param>
+        var caches = this.caches;
+
+        djstest.cleanStoreOnIndexedDb(caches, done);
+    };
+
+    var cleanupAllStorage = function (done) {
+        /// <summary>Cleans up all available storage mechanisms in the browser.</summary>
+        /// <param name="done" type="Function">Function to be called by each cleanup function after storage is cleared.</param>
+        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) {
+                /// <summary>Returns a cache created from the options object and </summary>
+                /// <param name="options" type="Object">Object to create a cache from.</param> 
+                var cache = datajs.createDataCache(options);
+                this.caches.push({ name: options.name, cache: cache });
+                return cache;
+            };
+
+            this.observableHttpClient = new ObservableHttpClient();
+            OData.defaultHttpClient = this.observableHttpClient;
+            this.caches = [];
+            var that = this;
+
+            djstest.wait(function (done) {
+                cleanupAllStorage.call(that, done);
+            });
+        },
+
+        teardown: function () {
+            OData.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) {
+            /// <summary>Returns the expected error message for the specified invalid value.</summary>
+            /// <param name="invalidValue type="Object">invalid value (anything other than zero or positive integer) to determine the error message from.</param>
+            /// <param name="parameterName" type="String">The name of the parameter being verified.</param>
+            /// <returns type="String">Error message expected.</returns>
+            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/48761e07/JSLib/tests/odata-cache-rx-functional-tests.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-rx-functional-tests.html b/JSLib/tests/odata-cache-rx-functional-tests.html
new file mode 100644
index 0000000..8635ec3
--- /dev/null
+++ b/JSLib/tests/odata-cache-rx-functional-tests.html
@@ -0,0 +1,54 @@
+<!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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.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/48761e07/JSLib/tests/odata-cache-rx-functional-tests.js
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-cache-rx-functional-tests.js b/JSLib/tests/odata-cache-rx-functional-tests.js
new file mode 100644
index 0000000..cb47982
--- /dev/null
+++ b/JSLib/tests/odata-cache-rx-functional-tests.js
@@ -0,0 +1,74 @@
+/// <reference path="../src/datajs.js" />
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/cache.js" />
+/// <reference path="common/djstest.js" />
+/// <reference path="common/ODataReadOracle.js" />
+/// <reference path="common/rx.js" />
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, application/atomsvc+xml;q=0.8, */*;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) {
+        /// <summary>Asserts two finite observables generate the same sequence</summary>
+        /// <param name="actual" type="IObservable">The actual observable</param>
+        /// <param name="expected" type="IObservable">The expected observable</param>
+        /// <param name="done" type="Function">The callback function when asserts are done</param>
+        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 = datajs.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/48761e07/JSLib/tests/odata-fuzz.html
----------------------------------------------------------------------
diff --git a/JSLib/tests/odata-fuzz.html b/JSLib/tests/odata-fuzz.html
new file mode 100644
index 0000000..5d9c35d
--- /dev/null
+++ b/JSLib/tests/odata-fuzz.html
@@ -0,0 +1,561 @@
+<!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="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.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) {
+            /// <summary>Fuzzes the specified string.</summary>
+            /// <param name="text" type="String">Text to fuzz.</param>
+            /// <returns type="String">The fuzzes text.</returns>
+
+            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) {
+                /// <summary>Performs a network request.</summary>
+                /// <param name="success" type="Function">Success callback with the response object.</param>
+                /// <param name="error" type="Function">Error callback with an error object.</param>
+                /// <returns type="Object">Object with an 'abort' method for the operation.</returns>
+
+                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>