You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ko...@apache.org on 2014/05/16 13:30:55 UTC

[01/13] [OLINGO-238] adopt odata-json-tests.js

Repository: olingo-odata4-js
Updated Branches:
  refs/heads/master 48761e07f -> 0367d2bcd


http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-xml-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-xml-tests.js b/datajs/tests/odata-xml-tests.js
new file mode 100644
index 0000000..64605e8
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/run-tests.wsf
----------------------------------------------------------------------
diff --git a/datajs/tests/run-tests.wsf b/datajs/tests/run-tests.wsf
new file mode 100644
index 0000000..26d954d
--- /dev/null
+++ b/datajs/tests/run-tests.wsf
@@ -0,0 +1,427 @@
+<!--
+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.
+-->
+<job>
+    <runtime>
+        <description>Test driver for running datajs tests - run from the same directory as the script</description>
+        <comment>
+            Result codes:
+            0 - success
+            1 - failed to launch tests
+            2 - tests failed
+        </comment>
+    </runtime>
+    <script language="JScript" src="test-list.js" />
+    <script language="JScript">
+
+        var exitCode;
+        var fso = WScript.CreateObject("Scripting.FileSystemObject");
+        var shell = WScript.CreateObject("WScript.Shell");
+
+        function attempt(action, interval, maxAttempts) {
+            /// <summary>Attempt an action at an interval, optionally for a maximum number of attempts</summary>
+            /// <param name="action">Action callback; should return boolean whether it succeeded</param>
+            /// <param name="interval">Interval (milliseconds) between attempts</param>
+            /// <param name="maxAttempts">(Optional) Maximum number of attempts. Infinite if undefined.</param>
+            /// <returns>Whether the action succeeded</returns>
+            var done = false;
+            var attempts = 0;
+            while (!done) {
+                var success = action();
+                if (maxAttempts !== undefined) {
+                    attempts++;
+                }
+                done = success === true || (maxAttempts !== undefined && attempts >= maxAttempts);
+                if (!done) {
+                    WScript.Sleep(interval);
+                }
+            }
+
+            return success;
+        }
+
+        function parseJson(text) {
+            /// <summary>Parses a JSON document, removes the 'd' wrapper.</summary>
+            try {
+                return eval("(" + text + ")").d;
+            } catch (e) {
+                throw { message: "Error parsing JSON: [" + text + "]" };
+            }
+        }
+
+        function SaveTextToFile(content, path) {
+            /// <summary>Saves text content into a file.</summary>
+            /// <param name="content" type="String">Content to save.</param>
+            /// <param name="path" type="String">Path of file to save into.</param>
+            var ForReading = 1, ForWriting = 2;
+            var file = fso.OpenTextFile(path, ForWriting, true, -1 /* open as unicode */);
+            file.Write(content);
+            file.Close();
+        }
+
+        function GetUrlSync(url) {
+            var xhr;
+            xhr = WScript.CreateObject("Msxml2.ServerXMLHTTP.6.0");
+            xhr.open("GET", url, false);
+            xhr.send();
+            return xhr.responseText;
+        }
+
+        function LaunchBrowser(browsers, serviceRoot, followingPages, url, testRunId, outputDirectory) {
+            /// <summary>Launches a browsers and waits until the service tells us the run is complete.</summary>
+            /// <param name="browsers">Browsers to run.</param>
+            /// <param name="serviceRoot" type="String">Root URL of the logging service.</param>
+            /// <param name="followingPages" type="Array">Array of pages that should follow the given url.</param>
+            /// <param name="url" type="String">URL of the page to start the browser on.</param>
+            /// <param name="testRunId" type="String">ID of the test run being monitored.</param>
+            /// <param name="outputDirectory" type="String">Directory in which to output screenshots.</param>
+
+            for (browserName in browsers) {
+                var xhr;
+                var markInProgressUrl = serviceRoot + "MarkInProgress?testRunId=" + testRunId;
+                GetUrlSync(markInProgressUrl);
+
+                // Add all the pages that follow the given URL.
+                if (followingPages && followingPages.length > 0) {
+                    var addFilesUrl = serviceRoot + "AddTestPages?testRunId=" + testRunId + "&pages=" + followingPages.join();
+                    GetUrlSync(addFilesUrl);
+                }
+
+                var setPrefixUrl = serviceRoot + "SetTestNamePrefix?testRunId=" + testRunId + "&prefix=" + browserName + "-";
+                GetUrlSync(setPrefixUrl);
+
+                exitCode = 0;
+                var response;
+
+                // Only the first location found from the browsers array is used. If none of the listed locations of the browser exist and the browser argument was 
+                // explicitly used then an exception is thrown.
+                var browserFound = false;
+                for (var i = 0; i < browsers[browserName].length && !browserFound; i++) {
+                    var path = shell.ExpandEnvironmentStrings(browsers[browserName][i]);
+                    if (fso.FileExists(path)) {
+                        browserFound = true;
+
+                        WScript.Echo("Navigating to " + url + " with " + path);
+                        var browser = shell.Exec("\"" + path + "\" " + url);
+
+                        var checkRunUrl = serviceRoot + "IsTestRunInProgress?testRunId=" + testRunId;
+                        WScript.Echo("Monitoring status on " + checkRunUrl);
+
+                        var interval = 2000;
+                        var maxAttempts = WScript.Arguments.Named.Exists("timeout") ? Math.floor((WScript.Arguments.Named.Item("timeout") / interval) * 1000) : undefined;
+                        var success = attempt(function () {
+                            return parseJson(GetUrlSync(checkRunUrl)) !== true;
+                        }, interval, maxAttempts);
+                        if (!success) {
+                            WScript.Echo("Timed out waiting for test to complete");
+                            exitCode = 2;
+                        }
+
+                        RunCommand("taskkill.exe /pid " + browser.ProcessID, true);
+                    }
+                }
+
+                // If the "/browsers" argument was explicitly used and all location have been checked, then throw an exception.
+                if (!browserFound) {
+                    var message = "Unable to find browser at: " + path;
+                    if (WScript.Arguments.Named.Exists("browsers")) {
+                        throw { message: message };
+                    } else {
+                        WScript.Echo(message);
+                    }
+                }
+            }
+        }
+
+        function WriteTestRunResults(serviceRoot, testRunId, outputDirectory) {
+            /// <summary>Writes the results of the test run to disk and updates the overall status.</summary>
+            /// <param name="serviceRoot" type="String">Root URL of the logging service.</param>
+            /// <param name="testRunId" type="String">ID of the test run being monitored.</param>
+            /// <param name="outputDirectory" type="String">Directory in which to write test result files.</param>
+
+            var getResultsUrl = serviceRoot + "GetTestRunResults?testRunId=" + testRunId;
+            WScript.Echo("Querying " + getResultsUrl);
+
+            var response = GetUrlSync(getResultsUrl);
+
+            var resultsPath = outputDirectory + "\\results.trx";
+            WScript.Echo("Writing results.trx file to " + resultsPath);
+            SaveTextToFile(response, resultsPath);
+
+            var xml = new ActiveXObject("Msxml2.DOMDocument.6.0");
+            xml.loadXML(response);
+            xml.setProperty("SelectionNamespaces", "xmlns:trx='http://microsoft.com/schemas/VisualStudio/TeamTest/2010'");
+            xml.setProperty("SelectionLanguage", "XPath");
+            var resultNode = xml.selectSingleNode("/trx:TestRun/trx:ResultSummary");
+            if (resultNode === null) {
+                throw { message: "Unable to find results summary" };
+            }
+
+            var outcome = resultNode.getAttribute("outcome");
+            if (outcome !== "Passed") {
+                WScript.Echo("Outcome: " + outcome);
+                var failedTests = xml.selectNodes("/trx:TestRun/trx:Results/trx:UnitTestResult[@outcome != 'Passed']/@testName");
+                for (var i = 0; i < failedTests.length; i++) {
+                    WScript.Echo("  Failed test: " + failedTests[i].value);
+                }
+                exitCode = 2;
+            } else {
+                WScript.Echo("All tests passed.");
+            }
+        }
+
+        function CheckUrl(url) {
+            var xhr = WScript.CreateObject("Msxml2.ServerXMLHTTP.6.0");
+            xhr.open("GET", url, false);
+            var success = false;
+            try {
+                xhr.send();
+                success = (xhr.status === 200);
+                if (!success) {
+                    WScript.Echo("status: " + xhr.status + " - " + xhr.statusText);
+                }
+            } catch (err) {
+                WScript.Echo("error: " + err.message);
+            }
+
+            return success;
+        }
+
+        function ExpandWildcard(path) {
+            var wcRegEx = /\\\*\*?\\/;
+            var wcMatch = wcRegEx.exec(path);
+
+            var paths = [];
+            if (wcMatch !== null) {
+                var recursive = wcMatch[0] === "\\**\\";
+                var basePath = path.substring(0, wcMatch.index);
+                var relativePath = path.substring(wcMatch.index + wcMatch[0].length);
+
+                if (fso.FolderExists(basePath)) {
+                    var folder = fso.GetFolder(basePath);
+                    var subFolders = new Enumerator(folder.SubFolders);
+
+                    paths = paths.concat(ExpandWildcard(basePath + "\\" + relativePath));
+
+                    for (; !subFolders.atEnd(); subFolders.moveNext()) {
+                        var expandedPath = subFolders.item().Path + "\\"
+                        if (recursive) {
+                            expandedPath += "**\\";
+                        }
+                        expandedPath += path.substring(wcMatch.index + wcMatch[0].length);
+                        paths = paths.concat(ExpandWildcard(expandedPath));
+                    }
+                }
+            } else {
+                paths.push(path);
+            }
+            return paths;
+        }
+
+        function FindFirstPath(candidates) {
+            /// <summary>Finds the first path present from a candidate list.</summary>
+            /// <param name="candidates" type="Array">Array of paths (possibly with environment variables).</param>
+            /// <returns type="String">The first folder on disk found; null if none are present.</returns>
+
+            var paths = [];
+
+            for (var i = 0; i < candidates.length; i++) {
+                var path = shell.ExpandEnvironmentStrings(candidates[i]);
+                paths = paths.concat(ExpandWildcard(path));
+            }
+
+            for (var i = 0; i < paths.length; i++) {
+                if (fso.FolderExists(paths[i]) || fso.FileExists(paths[i])) {
+                    return paths[i];
+                }
+            }
+            return null;
+        }
+
+        function RunCommand(command, waitForExit, expectedExitCode) {
+            /// <summary>Runs a command or program</summary>
+            /// <param name="command" type="String">Command to run</param>
+            /// <param name="waitForExit" type="Boolean">Whether to wait for program to exit</param>
+            /// <param name="expectedExitCode" type="Integer">If waitForExit is true, throw if the exit code is not expected</param>
+            /// <returns type="Integer">The exitcode if waitForExit is true; always 0 if waitForExit is false</returns>
+            WScript.Echo("[cmd] " + command);
+            var exitCode = shell.Run(command, 0, waitForExit);
+            if (expectedExitCode !== undefined && exitCode !== expectedExitCode) {
+                throw { message: "Process exited with unexpected exit code. (Expected: " + expectedExitCode + ", Actual: " + exitCode + ")" };
+            } else {
+                return exitCode;
+            }
+        }
+
+        function SetupWebDevServer() {
+            /// <summary>Starts up IIS Express if it's not running.</summary>
+            /// <returns type="String">The URL to the server root.</returns>
+            var siteName = "DataJS Development Site";
+            var appName = "datajs";
+            var port = "8989";
+            var result = "http://" + shell.ExpandEnvironmentStrings("%COMPUTERNAME%").toLowerCase() + ":" + port + "/" + appName + "/";
+            var url = result + "tests/common/TestLogger.svc";
+
+            var success = CheckUrl(url);
+
+            if (!success) {
+                // Assume that we need to launch this.
+                var src = fso.GetAbsolutePathName("..");
+
+                var folder = FindFirstPath([
+                    "%ProgramFiles(x86)%\\IIS Express",
+                    "%ProgramFiles%\\IIS Express"]);    
+
+                if (!folder) {
+                    throw { message: "Unable to find path to IIS Express" };
+                }
+
+                var appCmd = "\"" + folder + "\\appcmd.exe\"";
+                var iisExpress = "\"" + folder + "\\iisexpress.exe\"";
+
+                // Delete site if it already exists
+                WScript.Echo("Checking if site '" + siteName + "' already exists...");
+                if (RunCommand(appCmd + " list site \"" + siteName + "\"", true) === 0) {
+                    WScript.Echo("Deleting existing site '" + siteName + "'...");
+                    RunCommand(appCmd + " delete site \"" + siteName + "\"", true, 0);
+                }
+
+                // Create site and app
+                WScript.Echo("Creating site '" + siteName + "'...");
+                RunCommand(appCmd + " add site /name:\"" + siteName + "\" /bindings:http/*:" + port + ": /physicalPath:%IIS_BIN%\\AppServer\\empty_wwwroot", true, 0);
+
+                WScript.Echo("Creating application '" + appName + "'...");
+                RunCommand(appCmd + " add app /site.name:\"" + siteName + "\" /path:\"/" + appName + "\" /physicalPath:\"" + src + "\"", true, 0);
+
+                // Start the server
+                WScript.Echo("Starting IIS Express server...");
+                RunCommand(iisExpress + " /site:\"" + siteName + "\" /trace:error");
+
+                WScript.Sleep(2 * 1000);
+                success = attempt(function () {
+                    WScript.Echo("Waiting for server to come up, looking for " + url + " ...");
+                    return CheckUrl(url);
+                }, 5 * 1000, 3);
+
+                if (!success) {
+                    throw { message: "Unable to verify the URL at " + url };
+                }
+            }
+            return result;
+        }
+
+        function CreateTestRunId(serviceRoot) {
+            /// <summary>Creates a new test run ID from the service.</summary>
+            /// <param name="serviceRoot" type="String">Root of logger service.</param>
+            /// <returns type="String">The test run ID created.</returns>
+            var xhr = WScript.CreateObject("Msxml2.ServerXMLHTTP.6.0");
+            var url = serviceRoot + "CreateTestRun";
+            xhr.open("GET", url, false);
+            WScript.Echo("URL: " + url);
+            xhr.send();
+
+            var response = xhr.responseText;
+            var result = parseJson(response);
+            return result;
+        }
+
+        function GetBrowsers() {
+            /// <summary>Gets the browsers that should be used for running the tests.</summary>
+            /// <returns type="Object">Dictionary object containing the browser and its executable path as key value pairs.</returns>
+            var localAppData = fso.FolderExists(shell.ExpandEnvironmentStrings("%LOCALAPPDATA%")) ? "%LOCALAPPDATA%" : "%USERPROFILE%\\Local Settings\\Application Data";
+            var programFiles = fso.FolderExists(shell.ExpandEnvironmentStrings("%ProgramFiles(x86)%")) ? "%ProgramFiles(x86)%" : "%ProgramFiles%";
+            var browsers = {
+                IE8: [programFiles + "\\Internet Explorer\\iexplore.exe"],
+                Firefox4: [programFiles + "\\Mozilla Firefox\\firefox.exe"],
+                Chrome: [programFiles + "\\Google\\Chrome\\Application\\chrome.exe", localAppData + "\\Google\\Chrome\\Application\\chrome.exe"],
+                Safari5: [programFiles + "\\Safari\\safari.exe"],
+                Opera: [programFiles + "\\Opera\\opera.exe"]
+            };
+
+            var browsersToRun = {};
+
+            if (WScript.Arguments.Named.Exists("browsers")) {
+                browserNames = WScript.Arguments.Named.Item("browsers").split(',');
+                for (i in browserNames) {
+                    var browserName = browserNames[i];
+                    if (browsers[browserName]) {
+                        browsersToRun[browserName] = browsers[browserName];
+                    } else {
+                        throw { message: "Unknown browser: " + browserName };
+                    }
+                }
+            }
+            else {
+                browsersToRun = browsers;
+            }
+
+            return browsersToRun;
+        }
+
+        function GetTestFilesList() {
+            /// <summary>Gets the list of test files that are going to be executed in the test run.</summary>
+            /// <returns type="Array">The list of test files.</returns>
+            var testFilesList = null;
+            if (WScript.Arguments.Named.Exists("testFiles")) {
+                testFilesList = WScript.Arguments.Named.Item("testFiles").split(',');
+            }
+
+            if (testFilesList === null) {
+                testFilesList = getAllTestFiles();
+            }
+
+            WScript.Echo("Test files to be executed: " + testFilesList.toString());
+            return testFilesList;
+        }
+
+        function GetOutputDirectory() {
+            /// <summary>Gets the test run output directory.</summary>
+            /// <returns type="String">Output directory.</returns>
+            var result;
+            if (WScript.Arguments.Named.Exists("outputDirectory")) {
+                result = WScript.Arguments.Named.Item("outputDirectory");
+            } else {
+                result = shell.ExpandEnvironmentStrings("%DJSOUT%\\JSLib.sln\\tests");
+            }
+
+
+            return result;
+        }
+
+        try {
+            var root = SetupWebDevServer();
+            var serviceRoot = root + "tests/common/TestLogger.svc/";
+            var testRunId = CreateTestRunId(serviceRoot);
+            WScript.Echo("Test Run ID: " + testRunId);
+
+            var testFilesList = GetTestFilesList();
+            var browsers = GetBrowsers();
+            var outputDirectory = GetOutputDirectory();
+
+            if (testFilesList.length > 0) {
+                var url = root + "tests/" + testFilesList[0] + "?testRunId=" + testRunId;
+                LaunchBrowser(browsers, serviceRoot, testFilesList.splice(1, testFilesList.length), url, testRunId, outputDirectory);
+                WriteTestRunResults(serviceRoot, testRunId, outputDirectory);
+            }
+            else {
+                WScript.Echo("No test files specified to run.");
+            }
+        } catch (e) {
+            WScript.Echo("Error running tests");
+            for (var p in e) WScript.Echo(p + ": " + e[p]);
+            exitCode = 1;
+        }
+
+        WScript.Quit(exitCode);
+
+    </script>
+</job>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/store-indexeddb-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/store-indexeddb-tests.js b/datajs/tests/store-indexeddb-tests.js
new file mode 100644
index 0000000..76fbb30
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/store-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/store-tests.js b/datajs/tests/store-tests.js
new file mode 100644
index 0000000..1a05849
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/test-list.js
----------------------------------------------------------------------
diff --git a/datajs/tests/test-list.js b/datajs/tests/test-list.js
new file mode 100644
index 0000000..3a805d1
--- /dev/null
+++ b/datajs/tests/test-list.js
@@ -0,0 +1,20 @@
+// 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.
+
+// Test list for datajs tests
+
+function getAllTestFiles() {
+    return [
+        "odata-qunit-tests.htm"
+    ];
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/test-manager.html
----------------------------------------------------------------------
diff --git a/datajs/tests/test-manager.html b/datajs/tests/test-manager.html
new file mode 100644
index 0000000..fa4911a
--- /dev/null
+++ b/datajs/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


[06/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-cache-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-functional-tests.js b/datajs/tests/odata-cache-functional-tests.js
new file mode 100644
index 0000000..1a65c6f
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-cache-rx-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-rx-functional-tests.html b/datajs/tests/odata-cache-rx-functional-tests.html
new file mode 100644
index 0000000..8635ec3
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-cache-rx-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-rx-functional-tests.js b/datajs/tests/odata-cache-rx-functional-tests.js
new file mode 100644
index 0000000..cb47982
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-fuzz.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-fuzz.html b/datajs/tests/odata-fuzz.html
new file mode 100644
index 0000000..5d9c35d
--- /dev/null
+++ b/datajs/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>

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-handler-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-handler-tests.js b/datajs/tests/odata-handler-tests.js
new file mode 100644
index 0000000..7ec8a07
--- /dev/null
+++ b/datajs/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


[12/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/code/ReflectionDataContext.cs
----------------------------------------------------------------------
diff --git a/datajs/tests/code/ReflectionDataContext.cs b/datajs/tests/code/ReflectionDataContext.cs
new file mode 100644
index 0000000..eb70949
--- /dev/null
+++ b/datajs/tests/code/ReflectionDataContext.cs
@@ -0,0 +1,742 @@
+// 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;
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using Microsoft.OData.Service;
+    using Microsoft.OData.Service.Common;
+    using System.Globalization;
+    using System.Linq;
+    using System.Reflection;
+
+    /// <summary>
+    /// Provides a reflection-based, updatable data context.
+    /// </summary>
+    public abstract class ReflectionDataContext
+    {
+        // Fields
+        private List<object> deletedObjects = new List<object>();
+        private List<Action> pendingChanges;
+        private static Dictionary<Type, Dictionary<string, IList>> resourceSetsByContextTypeStorage = new Dictionary<Type, Dictionary<string, IList>>();
+
+        // Methods
+        protected ReflectionDataContext()
+        {
+            this.MetadataHelper = new ReflectionMetadataHelper(this);
+            this.pendingChanges = new List<Action>();
+            if (!resourceSetsByContextTypeStorage.ContainsKey(base.GetType()))
+            {
+                resourceSetsByContextTypeStorage.Add(base.GetType(), new Dictionary<string, IList>());
+                foreach (string resourceSetName in this.MetadataHelper.GetResourceSetNames())
+                {
+                    Type resourceType = this.MetadataHelper.GetResourceTypeOfSet(resourceSetName);
+                    IList listOfTInstance = Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { resourceType })) as IList;
+                    this.ResourceSetsStorage.Add(resourceSetName, listOfTInstance);
+                }
+            }
+            this.EnsureDataIsInitialized();
+        }
+
+        public virtual void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            ExceptionUtilities.CheckArgumentNotNull(resourceToBeAdded, "resourceToBeAdded");
+            UpdatableToken targetToken = UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            targetResource = targetToken.Resource;
+            resourceToBeAdded = UpdatableToken.AssertIsTokenAndResolve(resourceToBeAdded, "resourceToBeAdded");
+            IList list = this.GetValue(targetToken, propertyName) as IList;
+            ExceptionUtilities.CheckObjectNotNull(list, "Property '{0}' on type '{1}' was not a list", new object[] { propertyName, targetResource.GetType().Name });
+            this.pendingChanges.Add(delegate {
+                list.Add(resourceToBeAdded);
+            });
+        }
+
+        public virtual void ClearChanges()
+        {
+            this.pendingChanges.Clear();
+        }
+
+        public void ClearData()
+        {
+            this.ResourceSetsStorage.Clear();
+        }
+
+        private static bool CompareETagValues(Dictionary<string, object> resourceCookieValues, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
+        {
+            if (concurrencyValues.Count<KeyValuePair<string, object>>() != resourceCookieValues.Count)
+            {
+                return false;
+            }
+            foreach (KeyValuePair<string, object> keyValuePair in concurrencyValues)
+            {
+                if (!resourceCookieValues.ContainsKey(keyValuePair.Key))
+                {
+                    return false;
+                }
+                if (keyValuePair.Value == null)
+                {
+                    return (resourceCookieValues[keyValuePair.Key] == null);
+                }
+                if (!keyValuePair.Value.Equals(resourceCookieValues[keyValuePair.Key]))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public virtual object CreateResource(string containerName, string fullTypeName)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(fullTypeName, "fullTypeName");
+            UpdatableToken token = this.InstantiateResourceType(fullTypeName);
+            if (containerName != null)
+            {
+                this.pendingChanges.Add(delegate {
+                    this.GetResourceSetEntities(containerName).Add(token.Resource);
+                });
+            }
+            return token;
+        }
+
+        private void DeleteAllReferences(object targetResource)
+        {
+            foreach (string currentSetName in this.MetadataHelper.GetResourceSetNames())
+            {
+                Type currentEntityType = this.MetadataHelper.GetResourceTypeOfSet(currentSetName);
+                IList entitySetList = this.GetResourceSetEntities(currentSetName);
+                foreach (NavigationPropertyInfo navigationProperty in this.MetadataHelper.GetNavigationProperties(GetResourceTypeFullName(currentEntityType)))
+                {
+                    if (navigationProperty.CollectionElementType != null)
+                    {
+                        foreach (object currentEntityInstance in entitySetList)
+                        {
+                            this.RemoveResourceFromCollectionOnTargetResourceMatch(targetResource, navigationProperty, currentEntityInstance);
+                        }
+                    }
+                    else
+                    {
+                        ExceptionUtilities.CheckObjectNotNull(navigationProperty.PropertyInfo, "Invalid navigation property info", new object[0]);
+                        foreach (object currentEntityInstance in entitySetList)
+                        {
+                            this.SetEntityReferenceToNullOnTargetResourceMatch(targetResource, navigationProperty, currentEntityInstance);
+                        }
+                    }
+                }
+            }
+        }
+
+        public virtual void DeleteResource(object targetResource)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            targetResource = UpdatableToken.AssertIsTokenAndResolve(targetResource, "targetResource");
+            string resourceSetName = this.GetResourceSetOfTargetResource(targetResource);
+            ExceptionUtilities.CheckObjectNotNull(resourceSetName, "Unable to find set of the resource to delete", new object[0]);
+            this.deletedObjects.Add(targetResource);
+            IList resourceSetList = this.GetResourceSetEntities(resourceSetName);
+            this.DeleteAllReferences(targetResource);
+            this.pendingChanges.Add(delegate {
+                resourceSetList.Remove(targetResource);
+            });
+        }
+
+        protected abstract void EnsureDataIsInitialized();
+
+        protected virtual Type GetCollectionPropertyType(string fullTypeName, string propertyName)
+        {
+            Type type = this.MetadataHelper.FindClrTypeByFullName(fullTypeName);
+            Type collectionType = null;
+            if (type != null)
+            {
+                PropertyInfo property = type.GetProperty(propertyName);
+                if (property != null)
+                {
+                    collectionType = property.PropertyType;
+                }
+            }
+            return collectionType;
+        }
+
+        private Dictionary<string, object> GetConcurrencyValues(object targetResource)
+        {
+            Dictionary<string, object> etagValues = new Dictionary<string, object>();
+            foreach (string etagProperty in this.MetadataHelper.GetETagPropertiesOfType(GetResourceTypeFullName(targetResource.GetType())))
+            {
+                etagValues.Add(etagProperty, targetResource.GetType().GetProperty(etagProperty).GetValue(targetResource, null));
+            }
+            return etagValues;
+        }
+
+        public virtual object GetResource(IQueryable query, string fullTypeName)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(query, "query");
+            object resource = null;
+            foreach (object r in query)
+            {
+                ExceptionUtilities.Assert(resource == null, "Invalid Uri specified. The query '{0}' must refer to a single resource", new object[] { query.ToString() });
+                resource = r;
+            }
+            if (resource == null)
+            {
+                return null;
+            }
+            if (fullTypeName != null)
+            {
+                this.ValidateResourceType(resource, fullTypeName);
+            }
+            return new UpdatableToken(resource);
+        }
+
+        public IList<T> GetResourceSetEntities<T>(string resourceSetName)
+        {
+            return (IList<T>) this.GetResourceSetEntities(resourceSetName);
+        }
+
+        internal IList GetResourceSetEntities(string resourceSetName)
+        {
+            IList entities;
+            if (!this.ResourceSetsStorage.TryGetValue(resourceSetName, out entities))
+            {
+                Type elementType = this.MetadataHelper.GetResourceTypeOfSet(resourceSetName);
+                entities = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { elementType }));
+                this.ResourceSetsStorage[resourceSetName] = entities;
+            }
+            return entities;
+        }
+
+        private string GetResourceSetOfTargetResource(object targetResource)
+        {
+            foreach (string currentResourceSetName in this.MetadataHelper.GetResourceSetNames())
+            {
+                if (this.GetResourceSetEntities(currentResourceSetName).Contains(targetResource))
+                {
+                    return currentResourceSetName;
+                }
+            }
+            return null;
+        }
+
+        public static string GetResourceTypeFullName(Type type)
+        {
+            return type.FullName.Replace('+', '_');
+        }
+
+        public virtual object GetValue(object targetResource, string propertyName)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            UpdatableToken token = UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            if (token.PendingPropertyUpdates.ContainsKey(propertyName))
+            {
+                return token.PendingPropertyUpdates[propertyName];
+            }
+            targetResource = token.Resource;
+            PropertyInfo pi = targetResource.GetType().GetProperty(propertyName);
+            ExceptionUtilities.CheckObjectNotNull(pi, "Cannot find the property '{0}' on type '{1}'", new object[] { propertyName, targetResource.GetType().Name });
+            object value = pi.GetValue(targetResource, null);
+            if ((value != null) && (pi.PropertyType.Assembly == base.GetType().Assembly))
+            {
+                ExceptionUtilities.Assert(!this.MetadataHelper.IsTypeAnEntityType(pi.PropertyType), "GetValue should never be called for reference properties. Type was '{0}', property was '{1}'", new object[] { pi.PropertyType.FullName, propertyName });
+                value = new UpdatableToken(value);
+            }
+            return value;
+        }
+
+        private UpdatableToken InstantiateResourceType(string fullTypeName)
+        {
+            Type t = this.MetadataHelper.FindClrTypeByFullName(fullTypeName);
+            object instance = Activator.CreateInstance(t);
+            UpdatableToken token = new UpdatableToken(instance);
+            foreach (PropertyInfo p in t.GetProperties())
+            {
+                object generatedValue;
+                PropertyInfo property = p;
+                if (this.IsCollectionProperty(property))
+                {
+                    Type collectionType = this.GetCollectionPropertyType(GetResourceTypeFullName(t), property.Name);
+                    if (collectionType != null)
+                    {
+                        object newCollection = Activator.CreateInstance(collectionType);
+                        token.PendingPropertyUpdates[property.Name] = newCollection;
+                        this.pendingChanges.Add(delegate {
+                            property.SetValue(instance, newCollection, null);
+                        });
+                    }
+                }
+                if (this.TryGetStoreGeneratedValue(fullTypeName, property.Name, out generatedValue))
+                {
+                    token.PendingPropertyUpdates[property.Name] = generatedValue;
+                    this.pendingChanges.Add(delegate {
+                        property.SetValue(instance, generatedValue, null);
+                    });
+                }
+            }
+            return token;
+        }
+
+        protected virtual bool IsCollectionProperty(PropertyInfo propertyInfo)
+        {
+            return ((typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType) && (propertyInfo.PropertyType != typeof(string))) && (propertyInfo.PropertyType != typeof(byte[])));
+        }
+
+        public virtual void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            ExceptionUtilities.CheckArgumentNotNull(resourceToBeRemoved, "resourceToBeRemoved");
+            UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            resourceToBeRemoved = UpdatableToken.AssertIsTokenAndResolve(resourceToBeRemoved, "resourceToBeRemoved");
+            IList list = this.GetValue(targetResource, propertyName) as IList;
+            ExceptionUtilities.CheckObjectNotNull(list, "Property '{0}' on type '{1}' was not a list", new object[] { propertyName, targetResource.GetType().Name });
+            this.pendingChanges.Add(delegate {
+                list.Remove(resourceToBeRemoved);
+            });
+        }
+
+        private void RemoveResourceFromCollectionOnTargetResourceMatch(object targetResource, NavigationPropertyInfo navigationPropertyInfo, object currentEntityInstance)
+        {
+            IEnumerable childCollectionObject = navigationPropertyInfo.PropertyInfo.GetValue(currentEntityInstance, null) as IEnumerable;
+            if (childCollectionObject.Cast<object>().Any<object>(delegate (object o) {
+                return o == targetResource;
+            }))
+            {
+                MethodInfo removeMethod = navigationPropertyInfo.PropertyInfo.PropertyType.GetMethod("Remove");
+                this.pendingChanges.Add(delegate {
+                    removeMethod.Invoke(childCollectionObject, new object[] { targetResource });
+                });
+            }
+        }
+
+        public virtual object ResetResource(object resource)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+            UpdatableToken token = UpdatableToken.AssertIsToken(resource, "resource");
+            resource = token.Resource;
+            token = new UpdatableToken(resource);
+            object newInstance = Activator.CreateInstance(resource.GetType());
+            ExceptionUtilities.CheckObjectNotNull(newInstance, "Cannot reset resource because unable to creating new instance of type '{0}' returns null", new object[] { resource.GetType().Name });
+            foreach (string propertyToReset in this.MetadataHelper.GetPropertiesToReset(GetResourceTypeFullName(resource.GetType())))
+            {
+                PropertyInfo pi = newInstance.GetType().GetProperty(propertyToReset);
+                ExceptionUtilities.CheckObjectNotNull(pi, "Cannot reset resource because unable to find property '{0}'", new object[] { propertyToReset });
+                object newValue = pi.GetValue(newInstance, null);
+                this.pendingChanges.Add(delegate {
+                    pi.SetValue(resource, newValue, null);
+                });
+                token.PendingPropertyUpdates[propertyToReset] = newValue;
+            }
+            return token;
+        }
+
+        public virtual object ResolveResource(object resource)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+            return UpdatableToken.AssertIsTokenAndResolve(resource, "resource");
+        }
+
+        public virtual void SaveChanges()
+        {
+            foreach (Action pendingChange in this.pendingChanges)
+            {
+                pendingChange();
+            }
+            this.pendingChanges.Clear();
+            foreach (object deleted in this.deletedObjects)
+            {
+                foreach (object entity in this.ResourceSetsStorage.SelectMany<KeyValuePair<string, IList>, object>(delegate (KeyValuePair<string, IList> p) {
+                    return p.Value.Cast<object>();
+                }))
+                {
+                    ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, entity), "Found deleted entity!", new object[0]);
+                    foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties())
+                    {
+                        object value = propertyInfo.GetValue(entity, null);
+                        ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, value), "Found deleted entity!", new object[0]);
+                        IEnumerable enumerable = value as IEnumerable;
+                        if (enumerable != null)
+                        {
+                            foreach (object valueElement in enumerable.Cast<object>())
+                            {
+                                ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, valueElement), "Found deleted entity!", new object[0]);
+                            }
+                        }
+                    }
+                }
+            }
+            this.deletedObjects.Clear();
+        }
+
+        protected virtual void SetCollectionPropertyValue(object targetResource, PropertyInfo propertyInfo, IEnumerable propertyValue)
+        {
+            object collection;
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyInfo, "propertyInfo");
+            ExceptionUtilities.CheckArgumentNotNull(propertyValue, "propertyValue");
+            Type collectionType = this.GetCollectionPropertyType(GetResourceTypeFullName(propertyInfo.ReflectedType), propertyInfo.Name);
+            ExceptionUtilities.CheckObjectNotNull(collectionType, "Could not infer collection type for property", new object[0]);
+            propertyValue = propertyValue.Cast<object>().Select<object, object>(delegate (object o) {
+                return UpdatableToken.ResolveIfToken(o);
+            });
+            ConstructorInfo enumerableConstructor = collectionType.GetConstructor(new Type[] { typeof(IEnumerable) });
+            if (enumerableConstructor != null)
+            {
+                collection = enumerableConstructor.Invoke(new object[] { propertyValue });
+            }
+            else if (collectionType.IsGenericType && (collectionType.GetGenericArguments().Count<Type>() == 1))
+            {
+                Type typeArgument = collectionType.GetGenericArguments().Single<Type>();
+                ConstructorInfo typedEnumerableConstructor = collectionType.GetConstructor(new Type[] { typeof(IEnumerable<>).MakeGenericType(new Type[] { typeArgument }) });
+                if (typedEnumerableConstructor != null)
+                {
+                    object typedEnumerable = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(new Type[] { typeArgument }).Invoke(null, new object[] { propertyValue });
+                    collection = typedEnumerableConstructor.Invoke(new object[] { typedEnumerable });
+                }
+                else
+                {
+                    MethodInfo typedAddMethod = collectionType.GetMethod("Add", new Type[] { typeArgument });
+                    ExceptionUtilities.CheckObjectNotNull(typedAddMethod, "Could not find constructor or add method for type: " + collectionType.FullName, new object[0]);
+                    collection = Activator.CreateInstance(collectionType);
+                    foreach (object element in propertyValue)
+                    {
+                        typedAddMethod.Invoke(collection, new object[] { element });
+                    }
+                }
+            }
+            else
+            {
+                MethodInfo addMethod = collectionType.GetMethod("Add");
+                ExceptionUtilities.CheckObjectNotNull(addMethod, "Could not find constructor or add method for type: " + collectionType.FullName, new object[0]);
+                collection = Activator.CreateInstance(collectionType);
+                foreach (object element in propertyValue)
+                {
+                    addMethod.Invoke(collection, new object[] { element });
+                }
+            }
+            propertyInfo.SetValue(targetResource, collection, null);
+        }
+
+        public virtual void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(resourceCookie, "resourceCookie");
+            ExceptionUtilities.ThrowDataServiceExceptionIfFalse(checkForEquality.HasValue, 0x1a1, "Missing concurrency token for update operation", new object[0]);
+            ExceptionUtilities.Assert(checkForEquality.Value, "Should not be called with check for equality parameter equal to false", new object[0]);
+            ExceptionUtilities.CheckArgumentNotNull(concurrencyValues, "concurrencyValues");
+            if (concurrencyValues.Any<KeyValuePair<string, object>>())
+            {
+                resourceCookie = UpdatableToken.AssertIsTokenAndResolve(resourceCookie, "resourceCookie");
+                ExceptionUtilities.ThrowDataServiceExceptionIfFalse(CompareETagValues(this.GetConcurrencyValues(resourceCookie), concurrencyValues), 0x19c, "Concurrency tokens do not match", new object[0]);
+            }
+        }
+
+        private void SetEntityReferenceToNullOnTargetResourceMatch(object targetResource, NavigationPropertyInfo navigationPropertyInfo, object currentEntityInstance)
+        {
+            if (navigationPropertyInfo.PropertyInfo.GetValue(currentEntityInstance, null) == targetResource)
+            {
+                this.pendingChanges.Add(delegate {
+                    navigationPropertyInfo.PropertyInfo.SetValue(currentEntityInstance, null, null);
+                });
+            }
+        }
+
+        public virtual void SetReference(object targetResource, string propertyName, object propertyValue)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            if (propertyValue != null)
+            {
+                UpdatableToken.AssertIsToken(propertyValue, "propertyValue");
+            }
+            this.SetValue(targetResource, propertyName, propertyValue);
+        }
+
+        public virtual void SetValue(object targetResource, string propertyName, object propertyValue)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            UpdatableToken token = UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            targetResource = token.Resource;
+            token.PendingPropertyUpdates[propertyName] = propertyValue;
+            this.pendingChanges.Add(delegate {
+                object generatedValue;
+                Type t = targetResource.GetType();
+                PropertyInfo pi = t.GetProperty(propertyName);
+                ExceptionUtilities.CheckObjectNotNull(pi, "Unable to find property '{0}' on type '{1}'", new object[] { propertyName, targetResource.GetType().Name });
+                if (this.TryGetStoreGeneratedValue(GetResourceTypeFullName(t), propertyName, out generatedValue))
+                {
+                    propertyValue = generatedValue;
+                }
+                if (this.IsCollectionProperty(pi))
+                {
+                    ExceptionUtilities.CheckObjectNotNull(propertyValue, "Collection property value was null", new object[0]);
+                    IEnumerable enumerable = propertyValue as IEnumerable;
+                    ExceptionUtilities.CheckObjectNotNull(enumerable, "Collection property value was not an enumerable", new object[0]);
+                    this.SetCollectionPropertyValue(targetResource, pi, enumerable);
+                }
+                else
+                {
+                    propertyValue = UpdatableToken.ResolveIfToken(propertyValue);
+                    pi.SetValue(targetResource, propertyValue, null);
+                }
+            });
+        }
+
+        protected virtual bool TryGetStoreGeneratedValue(string fullTypeName, string propertyName, out object propertyValue)
+        {
+            propertyValue = null;
+            return false;
+        }
+
+        private void ValidateResourceType(object targetResource, string fullTypeName)
+        {
+            ExceptionUtilities.Assert(this.MetadataHelper.FindClrTypeByFullName(fullTypeName).IsAssignableFrom(targetResource.GetType()), "Invalid uri specified. expected type: '{0}', actual type: '{1}'", new object[] { fullTypeName, targetResource.GetType().FullName });
+        }
+
+        // Properties
+        internal ReflectionMetadataHelper MetadataHelper { get; set; }
+
+        internal Dictionary<string, IList> ResourceSetsStorage
+        {
+            get
+            {
+                Dictionary<string, IList> resourceSetsLookup = null;
+                Type currentContextType = base.GetType();
+                ExceptionUtilities.Assert(resourceSetsByContextTypeStorage.TryGetValue(currentContextType, out resourceSetsLookup), "Cannot find resource sets by the context type '{0}'", new object[] { currentContextType });
+                return resourceSetsLookup;
+            }
+        }
+
+        #region Inner types.
+    
+        internal class ReflectionMetadataHelper
+        {
+            // Fields
+            private ReflectionDataContext reflectionDataContext;
+
+            // Methods
+            public ReflectionMetadataHelper(ReflectionDataContext reflectionDataContext)
+            {
+                this.reflectionDataContext = reflectionDataContext;
+            }
+
+            public Type FindClrTypeByFullName(string resourceTypeFullName)
+            {
+                Type type = this.reflectionDataContext.GetType().Assembly.GetTypes().Where<Type>(delegate (Type t) {
+                    return (ReflectionDataContext.GetResourceTypeFullName(t) == resourceTypeFullName);
+                }).FirstOrDefault<Type>();
+                ExceptionUtilities.CheckObjectNotNull(type, "Unable to find type '{0}'", new object[] { resourceTypeFullName });
+                return type;
+            }
+
+            public string[] GetETagPropertiesOfType(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                List<string> etags = new List<string>();
+                foreach (ETagAttribute customAttribute in type.GetCustomAttributes(typeof(ETagAttribute), true))
+                {
+                    etags.AddRange(customAttribute.PropertyNames);
+                }
+                
+                return etags.ToArray();
+            }
+
+            public string[] GetKeyProperties(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                List<string> keyPropertyList = new List<string>();
+                foreach (PropertyInfo keyProperty in type.GetProperties().Where(pi => pi.Name.Contains("ID")))
+                {
+                    keyPropertyList.Add(keyProperty.Name);
+                }
+
+                foreach (DataServiceKeyAttribute customAttribute in type.GetCustomAttributes(typeof(DataServiceKeyAttribute), true))
+                {
+                    keyPropertyList.AddRange(customAttribute.KeyNames);
+                }
+                
+                return keyPropertyList.ToArray();
+            }
+
+            public NavigationPropertyInfo[] GetNavigationProperties(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                var navigationProperties = new List<NavigationPropertyInfo>();
+                var keyProperties = new List<string>(this.GetKeyProperties(fullTypeName));
+                foreach (PropertyInfo pi in type.GetProperties())
+                {
+                    if (!keyProperties.Contains(pi.Name))
+                    {
+                        if (this.IsTypeAnEntityType(pi.PropertyType))
+                        {
+                            navigationProperties.Add(new NavigationPropertyInfo(pi, null));
+                        }
+
+                        if (pi.PropertyType.IsGenericType && ((pi.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) || (pi.PropertyType.GetGenericTypeDefinition() == typeof(Collection<>))))
+                        {
+                            Type elementType = pi.PropertyType.GetGenericArguments()[0];
+                            if (this.IsTypeAnEntityType(elementType))
+                            {
+                                navigationProperties.Add(new NavigationPropertyInfo(pi, elementType));
+                            }
+                        }
+                    }
+                }
+
+                return navigationProperties.ToArray();
+            }
+
+            public string[] GetPropertiesToReset(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                var keyProperties = new List<string>(this.GetKeyProperties(fullTypeName));
+                var navigationProperties = new List<string>(this.GetNavigationProperties(fullTypeName).Select(ni =>ni.PropertyInfo.Name));
+                return type.GetProperties().Where(
+                    pi => !keyProperties.Contains(pi.Name) && !navigationProperties.Contains(pi.Name)
+                ).Select(pi => pi.Name).ToArray();
+            }
+
+            public string[] GetResourceSetNames()
+            {
+                return this.reflectionDataContext.GetType().GetProperties().Where(
+                    pi => pi.PropertyType.IsGenericType && (pi.PropertyType.GetGenericTypeDefinition() == typeof(IQueryable<>))
+                ).Select(pi => pi.Name).ToArray();
+            }
+
+            public Type GetResourceTypeOfSet(string resourceSetName)
+            {
+                PropertyInfo resourceSetPropertyInfo = this.reflectionDataContext.GetType().GetProperties().Where(pi => pi.Name == resourceSetName).FirstOrDefault();
+                ExceptionUtilities.CheckObjectNotNull(resourceSetPropertyInfo, "Error finding type of set '{0}'", new object[] { resourceSetName });
+                return resourceSetPropertyInfo.PropertyType.GetGenericArguments()[0];
+            }
+
+            public bool IsTypeAnEntityType(Type t)
+            {
+                foreach (string setName in this.GetResourceSetNames())
+                {
+                    if (this.GetResourceTypeOfSet(setName).IsAssignableFrom(t))
+                    {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        }
+
+        internal static class ExceptionUtilities
+        {
+            // Methods
+            public static void Assert(bool condition, string errorMessage, params object[] messageArguments)
+            {
+                if (!condition)
+                {
+                    throw new InvalidOperationException("Assertion failed: " + string.Format(CultureInfo.InvariantCulture, errorMessage, messageArguments));
+                }
+            }
+
+            public static void CheckArgumentNotNull(object argument, string argumentName)
+            {
+                if (argument == null)
+                {
+                    throw new ArgumentNullException(argumentName);
+                }
+            }
+
+            public static void CheckCollectionNotEmpty<TElement>(IEnumerable<TElement> argument, string argumentName)
+            {
+                CheckArgumentNotNull(argument, argumentName);
+                if (!argument.Any<TElement>())
+                {
+                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Collection argument '{0}' must have at least one element.", new object[] { argumentName }));
+                }
+            }
+
+            public static void CheckObjectNotNull(object value, string exceptionMessageFormatText, params object[] messageArguments)
+            {
+                Assert(exceptionMessageFormatText != null, "message cannnot be null", new object[0]);
+                Assert(messageArguments != null, "messageArguments cannnot be null", new object[0]);
+                if (value == null)
+                {
+                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, exceptionMessageFormatText, messageArguments));
+                }
+            }
+
+            public static void ThrowDataServiceExceptionIfFalse(bool condition, int statusCode, string errorMessage, params object[] messageArguments)
+            {
+                if (!condition)
+                {
+                    throw new DataServiceException(statusCode, string.Format(CultureInfo.InvariantCulture, errorMessage, messageArguments));
+                }
+            }
+        }
+
+        public class UpdatableToken
+        {
+            // Methods
+            public UpdatableToken(object resource)
+            {
+                ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+                this.Resource = resource;
+                this.PendingPropertyUpdates = new Dictionary<string, object>();
+            }
+
+            public static UpdatableToken AssertIsToken(object resource, string name)
+            {
+                ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+                UpdatableToken token = resource as UpdatableToken;
+                ExceptionUtilities.CheckObjectNotNull(token, "{0} was not a token. Type was: '{1}'", new object[] { name, resource.GetType() });
+                return token;
+            }
+
+            public static object AssertIsTokenAndResolve(object resource, string name)
+            {
+                return AssertIsToken(resource, name).Resource;
+            }
+
+            public static object ResolveIfToken(object resource)
+            {
+                UpdatableToken token = resource as UpdatableToken;
+                if (token != null)
+                {
+                    resource = token.Resource;
+                }
+                return resource;
+            }
+
+            // Properties
+            public IDictionary<string, object> PendingPropertyUpdates { get; set; }
+
+            public object Resource { get; set; }
+        }
+
+        internal class NavigationPropertyInfo
+        {
+            // Methods
+            internal NavigationPropertyInfo(PropertyInfo pi, Type collectionElementType)
+            {
+                this.PropertyInfo = pi;
+                this.CollectionElementType = collectionElementType;
+            }
+
+            // Properties
+            public Type CollectionElementType { get; set; }
+
+            public PropertyInfo PropertyInfo { get; set; }
+        }
+
+        #endregion Inner types.
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/code/atomreader.cs
----------------------------------------------------------------------
diff --git a/datajs/tests/code/atomreader.cs b/datajs/tests/code/atomreader.cs
new file mode 100644
index 0000000..1b4e762
--- /dev/null
+++ b/datajs/tests/code/atomreader.cs
@@ -0,0 +1,796 @@
+/// <summary>
+/// Class used to parse the Content section of the feed to return the properties data and metadata
+/// </summary>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.ServiceModel.Syndication;
+    using Microsoft.Spatial;
+    using System.Xml;
+    using System.Xml.Linq;
+
+    public static class AtomReader
+    {
+        const string atomXmlNs = "http://www.w3.org/2005/Atom";
+        const string gmlXmlNs = "http://www.opengis.net/gml";
+        const string odataRelatedPrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/related";
+        const string odataRelatedLinksPrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks";
+        const string odataXmlNs = "http://schemas.microsoft.com/ado/2007/08/dataservices";
+        const string odataMetaXmlNs = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
+        const string odataEditMediaPrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/edit-media";
+        const string odataMediaResourcePrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/mediaresource";
+
+        const string hrefAttribute = "href";
+        const string titleElement = "title";
+        const string workspaceElement = "workspace";
+        const string workspacesProperty = "workspaces";
+        const string collectionElement = "collection";
+        const string collectionsProperty = "collections";
+        const string extensionsProperty = "extensions";
+        static string baseUri = string.Empty;
+
+        /// <summary>
+        /// Creates a service document object
+        /// </summary>
+        /// <param name="container">The XML container</param>
+        /// <param name="uri">Uri to append to the href value</param>
+        /// <returns>The service document JsonObject</returns>
+        public static JsonObject ReadServiceDocument(TextReader payload, string baseUri)
+        {
+            JsonObject jsonObject = new JsonObject();
+            XElement container = XElement.Load(payload);
+
+            if (container != null && container.HasElements)
+            {
+                jsonObject["workspaces"] =
+                    container
+                        .Elements()
+                            .Where(element => element.Name.LocalName.Equals(workspaceElement))
+                            .Select(element => ReadWorkspaceObject(element, baseUri))
+                            .ToArray();
+
+                jsonObject["extensions"] =
+                    container
+                        .Elements()
+                            .Where(element => !element.Name.LocalName.Equals(workspaceElement))
+                            .Select(element => ReadExtensionElement(element))
+                            .ToArray();
+            }
+
+            return jsonObject;
+        }
+
+        public static JsonObject ReadEntry(TextReader payload)
+        {
+            SyndicationItem item = SyndicationItem.Load(XmlReader.Create(payload));
+            return ReadEntry(item);
+        }
+
+        public static JsonObject ReadFeed(TextReader payload)
+        {
+            SyndicationFeed feed = SyndicationFeed.Load(XmlReader.Create(payload));
+            JsonObject feedData = new JsonObject();
+            JsonObject feedMetadata = new JsonObject();
+
+            feedData["results"] = feed.Items.Select(item => ReadEntry(item)).ToArray();
+            feedData["__metadata"] = feedMetadata;
+
+            feedMetadata["feed_extensions"] = feed.AttributeExtensions.Select(pair => ReadExtension(pair)).ToArray();
+
+            if (feed.Id != null)
+            {
+                feedMetadata["uri"] = feed.Id;
+                feedMetadata["uri_extensions"] = new JsonObject[] { };
+            }
+
+            if (feed.Title != null)
+            {
+                feedMetadata["title"] = feed.Title.Text;
+                feedMetadata["title_extensions"] = GetTitleExtensions(feed.Title);
+            }
+
+            SyndicationLink feedSelfLink = GetLink("self", feed.Links);
+            if (feedSelfLink != null)
+            {
+                feedMetadata["self"] = feedSelfLink.GetAbsoluteUri().AbsoluteUri;
+                feedMetadata["self_extensions"] = GetLinkExtensions(feedSelfLink);
+            }
+
+            long? count = GetInlineCount(feed);
+            if (count.HasValue)
+            {
+                feedData["__count"] = count.Value;
+            }
+
+            SyndicationLink feedNextLink = GetLink("next", feed.Links);
+            if (feedNextLink != null)
+            {
+                feedData["__next"] = feedNextLink.GetAbsoluteUri().AbsoluteUri;
+                feedMetadata["next_extensions"] = GetLinkExtensions(feedNextLink);
+            }
+
+            return feedData;
+        }
+
+        private static JsonObject ReadEntry(SyndicationItem item)
+        {
+            SyndicationLink entryEditLink = GetLink("edit", item.Links);
+            SyndicationCategory entryCategory = item.Categories.FirstOrDefault();
+
+            XElement propertiesElement = GetPropertiesElement(item);
+            JsonObject entryData = ReadObject(propertiesElement);
+            entryData = JsonObject.Merge(entryData, ReadNavigationProperties(item));
+            entryData = JsonObject.Merge(entryData, ReadNamedStreams(item));
+
+            JsonObject propertiesMetadata = ReadPropertiesMetadata(propertiesElement);
+            propertiesMetadata = JsonObject.Merge(propertiesMetadata, ReadNavigationPropertiesMetadata(item));
+            propertiesMetadata = JsonObject.Merge(propertiesMetadata, ReadNamedStreamMetadata(item));
+
+            JsonObject entryMetadata = new JsonObject();
+            entryData["__metadata"] = entryMetadata;
+            entryMetadata["properties"] = propertiesMetadata;
+
+            if (item.Id != null)
+            {
+                entryMetadata["uri"] = item.Id;
+                entryMetadata["uri_extensions"] = new JsonObject[] { };
+            }
+
+            if (entryCategory != null)
+            {
+                entryMetadata["type"] = entryCategory.Name;
+                entryMetadata["type_extensions"] = new JsonObject[] { };
+            }
+
+            if (entryEditLink != null)
+            {
+                entryMetadata["edit"] = entryEditLink.GetAbsoluteUri().AbsoluteUri;
+                entryMetadata["edit_link_extensions"] = GetLinkExtensions(entryEditLink);
+            }
+
+            return entryData;
+        }
+
+        private static JsonObject ReadExtension(KeyValuePair<XmlQualifiedName, string> pair)
+        {
+            return ReaderUtils.CreateExtension(pair.Key.Name, pair.Key.Namespace, pair.Value);
+        }
+
+        private static string GetCollectionType(string type)
+        {
+            if (type != null && type.StartsWith("Collection("))
+            {
+                int start = 11;
+                int end = type.IndexOf(")") - 11;
+                return type.Substring(start, end);
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Find the m:properties element within a feed entry
+        /// </summary>
+        /// <param name="item">The feed entry</param>
+        /// <returns>The m:properties element</returns>
+        private static XElement GetPropertiesElement(SyndicationItem item)
+        {
+            // Check if the m:properties element is within the content element
+            XmlSyndicationContent xmlContent = item.Content as XmlSyndicationContent;
+            if (xmlContent != null)
+            {
+                XElement contentElement = XElement.Load(xmlContent.GetReaderAtContent());
+                return contentElement.Elements().FirstOrDefault(e => e.Name == XName.Get("properties", odataMetaXmlNs));
+            }
+            // If we're here, then we are dealing with a feed that has an MLE
+            // i.e. the m:properties element is a peer of the content element, and shows up
+            // in the elementExtensions instead
+            SyndicationElementExtension propertiesElementExtension = item.ElementExtensions.FirstOrDefault(e => e.OuterName.Equals("properties"));
+            if (propertiesElementExtension != null)
+            {
+                XNode propertiesElement = XNode.ReadFrom(propertiesElementExtension.GetReader());
+                return (XElement)propertiesElement;
+            }
+
+            throw new NotSupportedException("Unsupported feed entry format");
+        }
+
+        /// <summary>
+        /// Gets the inline count within a feed
+        /// </summary>
+        /// <param name="feed">The feed</param>
+        /// <returns>The inline count, or null if none exists</returns>
+        private static long? GetInlineCount(SyndicationFeed feed)
+        {
+            SyndicationElementExtension countElementExtension = feed.ElementExtensions.SingleOrDefault(extension =>
+                extension.OuterName.Equals("count", StringComparison.OrdinalIgnoreCase) &&
+                extension.OuterNamespace.Equals(odataMetaXmlNs));
+
+            if (countElementExtension != null)
+            {
+                XElement countElement = (XElement)XNode.ReadFrom(countElementExtension.GetReader());
+                return XmlConvert.ToInt64(countElement.Value);
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Gets the link with the specified relationship type
+        /// </summary>
+        /// <param name="rel">Relationship type</param>
+        /// <param name="links">The set of links to search from</param>
+        /// <returns>The link with the specified relationship type, or null if none exists</returns>
+        private static SyndicationLink GetLink(string rel, IEnumerable<SyndicationLink> links)
+        {
+            return links.SingleOrDefault(link => link.RelationshipType.Equals(rel, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        private static IEnumerable<SyndicationLink> GetLinks(string rel, IEnumerable<SyndicationLink> links)
+        {
+            return links.Where(link => link.RelationshipType.StartsWith(rel, StringComparison.Ordinal));
+        }
+
+        //TODO refactor the extraction of extensions into extension elements and extension attribute methods.
+        private static JsonObject[] GetLinkExtensions(SyndicationLink link)
+        {
+            List<JsonObject> extensions = new List<JsonObject>();
+            //TODO: fix the inclusion of title as extension.  Title attribute is not required in the link element and its
+            //inclusion as an extension should be tested for the precesence of the attribute in the xml element.  Unfortunately,
+            //SyndicationLink doesn't allow for accessing the underlying XML document.. perhaps using an AtomFormatter10?? 
+            extensions.Add(ReaderUtils.CreateExtension("title", null, link.Title));
+            extensions.AddRange(link.AttributeExtensions.Select(pair => ReadExtension(pair)));
+
+            return extensions.ToArray();
+        }
+
+        private static JsonObject[] GetTitleExtensions(TextSyndicationContent title)
+        {
+            List<JsonObject> extensions = new List<JsonObject>();
+            extensions.Add(ReaderUtils.CreateExtension("type", null, title.Type));
+            extensions.AddRange(title.AttributeExtensions.Select(pair => ReadExtension(pair)));
+
+            return extensions.ToArray();
+        }
+
+        /// <summary>
+        /// Gets the "type" value from a property element
+        /// </summary>
+        /// <param name="propertyElement">The property element</param>
+        /// <returns>The "type" value, or default (Edm.String) if none specified</returns>
+        private static string GetTypeAttribute(XElement propertyElement)
+        {
+            XAttribute typeAttribute = propertyElement.Attribute(XName.Get("type", odataMetaXmlNs));
+            if (typeAttribute == null)
+            {
+                if (propertyElement.HasElements)
+                {
+                    return null;
+                }
+                return "Edm.String";
+            }
+            return typeAttribute.Value;
+        }
+
+        private static bool HasTypeAttribute(XElement propertyElement)
+        {
+            return propertyElement.Attribute(XName.Get("type", odataMetaXmlNs)) != null;
+        }
+
+        private static bool IsCollectionProperty(XElement propertyElement)
+        {
+            string type = GetTypeAttribute(propertyElement);
+            if (type != null && type.StartsWith("Collection("))
+            {
+                return true;
+
+            }
+            return propertyElement.Elements().Count(pe => pe.Name == XName.Get("element", odataXmlNs)) > 1;
+        }
+
+        private static JsonObject ReadWorkspaceObject(XElement container, string baseUri)
+        {
+            JsonObject jsonObject = new JsonObject();
+
+            jsonObject["collections"] =
+                container
+                    .Elements()
+                        .Where(element => element.Name.LocalName.Equals("collection"))
+                        .Select(element => ReadWorkspaceCollections(element, baseUri))
+                        .ToArray();
+
+            jsonObject["extensions"] =
+                container
+                    .Elements()
+                        .Where(element =>
+                            !(element.Name.LocalName.Equals("collection") ||
+                              element.Name.LocalName.Equals("title")))
+                        .Select(element => ReadExtensionElement(element))
+                        .ToArray();
+
+            jsonObject["title"] =
+                container
+                    .Elements()
+                    .Where(element => element.Name.LocalName.Equals("title"))
+                    .First()
+                    .Value;
+
+            return jsonObject;
+        }
+
+        private static JsonObject ReadWorkspaceCollections(XElement container, string baseUri)
+        {
+            JsonObject jsonObject = new JsonObject();
+            string title = string.Empty;
+
+            jsonObject["extensions"] =
+                container
+                    .Elements()
+                        .Where(element =>
+                            !(element.Name.LocalName.Equals(collectionElement) ||
+                              element.Name.LocalName.Equals(titleElement)))
+                        .Select(element => ReadExtensionElement(element))
+                        .ToArray();
+
+            jsonObject["title"] =
+                container
+                    .Elements()
+                        .Where(element => element.Name.LocalName.Equals("title"))
+                        .First()
+                        .Value;
+
+            IEnumerable<XAttribute> hrefAttributes =
+                container
+                    .Attributes()
+                        .Where(element => element.Name.LocalName.Equals("href"));
+
+            jsonObject["href"] = baseUri + hrefAttributes.First().Value;
+
+            return jsonObject;
+        }
+
+        private static JsonObject ReadExtensionElement(XElement element)
+        {
+            JsonObject jsonObject = ReaderUtils.CreateExtension(element.Name.LocalName, element.BaseUri, null);
+            jsonObject.Remove("value");
+            jsonObject["attributes"] = ReadExtensionAttributes(element);
+            jsonObject["children"] = element.Elements().Select(child => ReadExtensionElement(element)).ToArray();
+
+            return jsonObject;
+        }
+
+        private static JsonObject ReadExtensionAttribute(XAttribute attribute)
+        {
+            return ReaderUtils.CreateExtension(attribute.Name.LocalName, attribute.BaseUri, attribute.Value);
+        }
+
+        private static JsonObject[] ReadExtensionAttributes(XElement container)
+        {
+            List<JsonObject> attributes = new List<JsonObject>();
+            foreach (XAttribute attribute in container.Attributes())
+            {
+                attributes.Add(ReadExtensionAttribute(attribute));
+            }
+            return attributes.ToArray();
+        }
+
+        private static JsonObject ReadNamedStreamMetadata(SyndicationItem item)
+        {
+            JsonObject propertiesMetadata = new JsonObject();
+            JsonObject streamMetadata;
+            string propertyName;
+
+            foreach (SyndicationLink link in GetLinks(odataEditMediaPrefix, item.Links))
+            {
+                streamMetadata = new JsonObject();
+                streamMetadata["edit_media_extensions"] = GetLinkExtensions(link);
+                streamMetadata["media_src_extensions"] = new JsonObject[0];
+
+                propertyName = link.RelationshipType.Substring(odataEditMediaPrefix.Length + 1);
+                propertiesMetadata[propertyName] = streamMetadata;
+            }
+
+            foreach (SyndicationLink link in GetLinks(odataMediaResourcePrefix, item.Links))
+            {
+                streamMetadata = new JsonObject();
+                streamMetadata["media_src_extensions"] = GetLinkExtensions(link);
+
+                propertyName = link.RelationshipType.Substring(odataMediaResourcePrefix.Length + 1);
+                if (propertiesMetadata.ContainsKey(propertyName))
+                {
+                    streamMetadata = JsonObject.Merge((JsonObject)propertiesMetadata[propertyName], streamMetadata);
+                }
+                propertiesMetadata[propertyName] = streamMetadata;
+            }
+            return propertiesMetadata;
+        }
+
+        private static JsonObject ReadNamedStreams(SyndicationItem item)
+        {
+            // Not very elegant, but quick and easy, do it in two passes.
+            JsonObject streams = new JsonObject();
+            JsonObject streamValue;
+            JsonObject mediaResource;
+            string propertyName;
+
+            foreach (SyndicationLink link in GetLinks(odataEditMediaPrefix, item.Links))
+            {
+                propertyName = link.RelationshipType.Substring(odataEditMediaPrefix.Length + 1);
+                streamValue = new JsonObject();
+                mediaResource = new JsonObject();
+
+                streams[propertyName] = streamValue;
+
+                streamValue["__mediaresource"] = mediaResource;
+
+                mediaResource["edit_media"] = link.GetAbsoluteUri().AbsoluteUri;
+                mediaResource["content_type"] = link.MediaType;
+                mediaResource["media_src"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                var etagAttributeName = new XmlQualifiedName("etag", odataMetaXmlNs);
+                if (link.AttributeExtensions.ContainsKey(etagAttributeName))
+                {
+                    mediaResource["media_etag"] = link.AttributeExtensions[etagAttributeName];
+                    link.AttributeExtensions.Remove(etagAttributeName);
+                }
+            }
+
+            foreach (SyndicationLink link in GetLinks(odataMediaResourcePrefix, item.Links))
+            {
+                propertyName = link.RelationshipType.Substring(odataMediaResourcePrefix.Length + 1);
+                mediaResource = new JsonObject();
+                mediaResource["content_type"] = link.MediaType;
+                mediaResource["media_src"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                if (streams.ContainsKey(propertyName))
+                {
+                    streamValue = streams[propertyName] as JsonObject;
+                    mediaResource = JsonObject.Merge(streamValue["__mediaresource"] as JsonObject, mediaResource);
+                }
+                else
+                {
+                    streamValue = new JsonObject();
+                }
+                streamValue["__mediaresource"] = mediaResource;
+                streams[propertyName] = streamValue;
+            }
+            return streams;
+        }
+
+        private static JsonObject ReadNavigationProperties(SyndicationItem item)
+        {
+            JsonObject navProperties = new JsonObject();
+            SyndicationElementExtension inline;
+
+            string propertyName;
+            JsonObject propertyValue = null;
+
+            foreach (SyndicationLink link in GetLinks(odataRelatedPrefix, item.Links))
+            {
+                propertyName = link.RelationshipType.Substring(odataRelatedPrefix.Length + 1);
+                inline = link.ElementExtensions.SingleOrDefault(e =>
+                    odataMetaXmlNs.Equals(e.OuterNamespace, StringComparison.Ordinal) &&
+                    e.OuterName.Equals("inline", StringComparison.Ordinal));
+
+                if (inline != null)
+                {
+                    XElement inlineElement = (XElement)XNode.ReadFrom(inline.GetReader());
+                    XElement innerElement = inlineElement.Elements().FirstOrDefault();
+
+                    if (innerElement != null)
+                    {
+                        // By default the inner feed/entry does not have the xml:base attribute, so we need to
+                        // add it so that the parsed SyndicationFeed or SyndicationItem retains the baseUri
+                        if (link.BaseUri != null)
+                        {
+                            innerElement.SetAttributeValue(XNamespace.Xml + "base", link.BaseUri.OriginalString);
+                        }
+
+                        // We are converting to a string before creating the reader to strip out extra indenting,
+                        // otherwise the reader creates extra XmlText nodes that SyndicationFeed/SyndicationItem cannot handle
+                        try
+                        {
+                            propertyValue = ReadFeed(new StringReader(innerElement.ToString()));
+                        }
+                        catch (XmlException)
+                        {
+                            // Try with entry instead .. 
+
+                            propertyValue = ReadEntry(new StringReader(innerElement.ToString()));
+                        }
+                    }
+                }
+                else
+                {
+                    JsonObject deferred = new JsonObject();
+                    deferred["uri"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                    propertyValue = new JsonObject();
+                    propertyValue["__deferred"] = deferred;
+                }
+                navProperties[propertyName] = propertyValue;
+            }
+            return navProperties;
+        }
+
+        private static JsonObject ReadNavigationPropertiesMetadata(SyndicationItem item)
+        {
+            JsonObject propertiesMetadata = new JsonObject();
+            JsonObject navMetadata;
+            string propertyName;
+
+            foreach (SyndicationLink link in GetLinks(odataRelatedPrefix, item.Links))
+            {
+                navMetadata = new JsonObject();
+                navMetadata["extensions"] = GetLinkExtensions(link).Where(e =>
+                    !(string.Equals(e["name"] as string, "inline", StringComparison.Ordinal) &&
+                        string.Equals(e["namespaceURI"] as string, odataMetaXmlNs, StringComparison.Ordinal))
+                ).ToArray();
+
+                propertyName = link.RelationshipType.Substring(odataRelatedPrefix.Length + 1);
+                propertiesMetadata[propertyName] = navMetadata;
+            }
+
+            foreach (SyndicationLink link in GetLinks(odataRelatedLinksPrefix, item.Links))
+            {
+                navMetadata = new JsonObject();
+                navMetadata["associationuri"] = link.GetAbsoluteUri().AbsoluteUri;
+                navMetadata["associationuri_extensions"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                propertyName = link.RelationshipType.Substring(odataRelatedLinksPrefix.Length + 1);
+                if (propertiesMetadata.ContainsKey(propertyName))
+                {
+                    navMetadata = JsonObject.Merge(propertiesMetadata[propertyName] as JsonObject, navMetadata);
+                }
+                propertiesMetadata[propertyName] = navMetadata;
+            }
+
+            return propertiesMetadata;
+        }
+
+        private static JsonObject ReadPropertiesMetadata(XElement container)
+        {
+            JsonObject json = null;
+            if (container != null && container.Elements().Any(e => e.Name.NamespaceName == odataXmlNs))
+            {
+                json = new JsonObject();
+                foreach (XElement propertyElement in container.Elements())
+                {
+                    json[propertyElement.Name.LocalName] = ReadPropertyMetadata(propertyElement);
+                }
+            }
+            return json;
+        }
+
+        private static JsonObject ReadPropertyMetadata(XElement property)
+        {
+            var metadata = ReaderUtils.CreateEntryPropertyMetadata(GetTypeAttribute(property));
+
+            if (IsCollectionProperty(property))
+            {
+                string collectionType = GetCollectionType(GetTypeAttribute(property));
+                if (collectionType == null)
+                {
+                    metadata["type"] = "Collection()";
+                }
+
+                List<JsonObject> elements = new List<JsonObject>();
+                foreach (XElement item in property.Elements(XName.Get("element", odataXmlNs)))
+                {
+                    string itemType =
+                        HasTypeAttribute(item) ? GetTypeAttribute(item) :
+                        IsCollectionProperty(item) ? "Collection()" : collectionType;
+
+                    var itemMetadata = ReaderUtils.CreateEntryPropertyMetadata(itemType);
+                    if (item.Elements().Any(e => e.Name.NamespaceName == odataXmlNs))
+                    {
+                        itemMetadata["properties"] = ReadPropertiesMetadata(item);
+                    }
+                    elements.Add(itemMetadata);
+                }
+                metadata["elements"] = elements.ToArray();
+            }
+            else if (property != null && property.Elements().Any(e => e.Name.NamespaceName == odataXmlNs))
+            {
+                metadata["properties"] = ReadPropertiesMetadata(property);
+            }
+
+            return metadata;
+        }
+
+        /// <summary>
+        /// Creates a JsonObject from an XML container element (e.g. the m:properties element) of an OData ATOM feed entry. 
+        /// </summary>
+        /// <param name="container">The XML container</param>
+        /// <param name="buildValue">Function that builds a value from a property element</param>
+        /// <returns>The JsonObject containing the name-value pairs</returns>
+        private static JsonObject ReadObject(XElement container)
+        {
+            if (container == null)
+            {
+                return null;
+            }
+
+            var json = new JsonObject();
+            foreach (XElement propertyElement in container.Elements())
+            {
+                json[propertyElement.Name.LocalName] = ReadDataItem(propertyElement);
+            }
+            return json;
+        }
+
+        private static JsonObject ReadCollectionProperty(XElement property, string typeName)
+        {
+            var collectionType = GetCollectionType(typeName);
+
+            var json = new JsonObject();
+            var results = new List<object>();
+
+            foreach (XElement item in property.Elements())
+            {
+                object resultItem = ReadDataItem(item);
+                results.Add(resultItem);
+
+                JsonObject complexValue = resultItem as JsonObject;
+                if (complexValue != null)
+                {
+                    var metadata = complexValue["__metadata"] as JsonObject;
+                    if (!string.IsNullOrEmpty(collectionType) && metadata["type"] == null)
+                    {
+                        metadata["type"] = collectionType;
+                    }
+                }
+            }
+
+            json["results"] = results;
+            json["__metadata"] = ReaderUtils.CreateEntryPropertyMetadata(typeName, false);
+
+            return json;
+        }
+
+        private static JsonObject ReadComplexProperty(XElement container, string typeName)
+        {
+            JsonObject json = ReadObject(container);
+            json["__metadata"] = ReaderUtils.CreateEntryPropertyMetadata(GetTypeAttribute(container), false);
+            return json;
+        }
+
+        private static JsonObject ReadJsonSpatialProperty(XElement container, XElement gmlValue, bool isGeography)
+        {
+            GmlFormatter gmlFormatter = GmlFormatter.Create();
+            GeoJsonObjectFormatter jsonformatter = GeoJsonObjectFormatter.Create();
+
+            bool ignoreCrc = !gmlValue.Attributes().Any(a => a.Name.LocalName == "srsName");
+
+            ISpatial spatialValue;
+            if (isGeography)
+            {
+                spatialValue = gmlFormatter.Read<Geography>(gmlValue.CreateReader());
+            }
+            else
+            {
+                spatialValue = gmlFormatter.Read<Geometry>(gmlValue.CreateReader());
+            }
+
+            IDictionary<string, object> geoJsonData = jsonformatter.Write(spatialValue);
+            JsonObject json = new JsonObject();
+
+            Queue<object> geoJsonScopes = new Queue<object>();
+            Queue<object> jsonScopes = new Queue<object>();
+
+            geoJsonScopes.Enqueue(geoJsonData);
+            jsonScopes.Enqueue(json);
+
+            Func<object, object> convertScope = (scope) =>
+            {
+                object newScope =
+                        scope is List<object> || scope is object[] ? (object)new List<Object>() :
+                        scope is IDictionary<string, object> ? (object)new JsonObject() :
+                        null;
+
+                if (newScope != null)
+                {
+                    geoJsonScopes.Enqueue(scope);
+                    jsonScopes.Enqueue(newScope);
+                }
+
+                return newScope ?? scope;
+            };
+
+            while (jsonScopes.Count > 0)
+            {
+                if (jsonScopes.Peek() is JsonObject)
+                {
+                    var currentGeoJson = (IDictionary<string, object>)geoJsonScopes.Dequeue();
+                    var currentJson = (JsonObject)jsonScopes.Dequeue();
+
+                    foreach (var item in currentGeoJson)
+                    {
+                        if (!ignoreCrc || item.Key != "crs")
+                        {
+                            currentJson[item.Key] = convertScope(item.Value);
+                        }
+                    }
+                }
+                else
+                {
+                    var currentGeoJson = (IEnumerable<object>)geoJsonScopes.Dequeue();
+                    var currentJson = (List<object>)jsonScopes.Dequeue();
+
+                    foreach (var item in currentGeoJson)
+                    {
+                        currentJson.Add(convertScope(item));
+                    }
+                }
+            }
+            json["__metadata"] = ReaderUtils.CreateEntryPropertyMetadata(GetTypeAttribute(container), false);
+            return json;
+        }
+
+        public static object ReadDataItem(XElement item)
+        {
+            string typeName = GetTypeAttribute(item);
+            XElement gmlRoot = item.Elements().SingleOrDefault(e => e.Name.NamespaceName == gmlXmlNs);
+
+            if (gmlRoot != null)
+            {
+                bool isGeography = typeName.StartsWith("Edm.Geography");
+                return ReadJsonSpatialProperty(item, gmlRoot, isGeography);
+            }
+
+            bool isCollection = IsCollectionProperty(item);
+            if (item.HasElements || isCollection)
+            {
+                // Complex type, Collection Type: parse recursively
+                return isCollection ? ReadCollectionProperty(item, typeName) : ReadComplexProperty(item, typeName);
+            }
+
+            // Primitive type: null value
+            XNamespace mNamespace = item.GetNamespaceOfPrefix("m");
+            XAttribute nullAttribute = mNamespace == null ? null : item.Attribute(mNamespace.GetName("null"));
+            if (nullAttribute != null && nullAttribute.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return null;
+            }
+
+            // Primitive type: check type and parse value accordingly
+            string value = item.Value;
+            switch (typeName)
+            {
+                case "Edm.Byte":
+                    return XmlConvert.ToByte(value);
+                case "Edm.Int16":
+                    return XmlConvert.ToInt16(value);
+                case "Edm.Int32":
+                    return XmlConvert.ToInt32(value);
+                case "Edm.SByte":
+                    return XmlConvert.ToSByte(value);
+                case "Edm.Boolean":
+                    return XmlConvert.ToBoolean(value);
+                case "Edm.Double":
+                    return XmlConvert.ToDouble(value);
+                case "Edm.Single":
+                    return XmlConvert.ToSingle(value);
+                case "Edm.Guid":
+                    return XmlConvert.ToGuid(value);
+                case "Edm.DateTime":
+                    return new JsDate(XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.Utc));
+                case "Edm.DateTimeOffset":
+                    return new JsDate(XmlConvert.ToDateTimeOffset(value));
+                case "Edm.Time":
+                    throw new NotSupportedException(typeName + " is not supported");
+                // Decimal and Int64 values are sent as strings over the wire.  This is the same behavior as WCF Data Services JSON serializer.
+                case "Edm.Decimal":
+                case "Edm.Int64":
+                case "":
+                default:
+                    return value;
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/code/csdlreader.cs
----------------------------------------------------------------------
diff --git a/datajs/tests/code/csdlreader.cs b/datajs/tests/code/csdlreader.cs
new file mode 100644
index 0000000..253cf10
--- /dev/null
+++ b/datajs/tests/code/csdlreader.cs
@@ -0,0 +1,186 @@
+/// <summary>
+/// Class used to parse csdl to create the metatdata object
+/// </summary>
+/// 
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.Xml.Linq;
+
+
+    public static class CsdlReader
+    {
+        static readonly string knownNamespace = "http://docs.oasis-open.org";
+        static readonly string[] repeatingElements = 
+            {
+                "Action",
+                "ActionImport",
+                "Annotation",
+                "Annotations",
+                "Apply",
+                "Binary",
+                "Bool",
+                "Cast",
+                "Collection",
+                "ComplexType",
+                "Date",
+                "DateTimeOffset",
+                "Decimal",
+                "Duration",
+                "EntitySet",
+                "EntityType",
+                "EnumMember",
+                "EnumType",
+                "Float",
+                "Function",
+                "FunctionImport",
+                "Guid",
+                "If",
+                "Int",
+                "IsOf",
+                "Key",
+                "LabeledElement",
+                "LabeledElementReference",
+                "Member",
+                "NavigationProperty",
+                "NavigationPropertyBinding",
+                "NavigationPropertyPath",
+                "Null",
+                "OnDelete",
+                "Path",
+                "Parameter",
+                "Property",
+                "PropertyPath",
+                "PropertyRef",
+                "PropertyValue",
+                "Record",
+                "ReferentialConstraint",
+                "String",
+                "Schema",
+                "Singleton",
+                "Term",
+                "TimeOfDay",
+                "TypeDefinition",
+                "UrlRef",
+                "Reference",
+                "Include",
+                "IncludeAnnotations"
+            };
+
+        public static Dictionary<string, object> ReadCsdl(TextReader payload)
+        {
+            return BuildElementJsonObject(XElement.Load(payload));
+        }
+
+        /// <summary>
+        /// Build the attribute object 
+        /// </summary>
+        /// <param name="xmlAttributes">IEnumberable of XAttributes to build the attribute object</param>
+        /// <returns>The JsonObject containing the name-value pairs for an element's attributes</returns>
+        static Dictionary<string, object> BuildAttributeJsonObject(IEnumerable<XAttribute> xmlAttributes)
+        {
+            Dictionary<string, object> jsonObject = new Dictionary<string, object>();
+
+            foreach (XAttribute attribute in xmlAttributes)
+            {
+                if (!attribute.IsNamespaceDeclaration)
+                {
+                    string attributeNamespace = attribute.Name.Namespace.ToString();
+                    if (string.IsNullOrEmpty(attributeNamespace) ||
+                        attributeNamespace.StartsWith(knownNamespace, StringComparison.InvariantCultureIgnoreCase))
+                    {
+                        jsonObject[MakeFirstLetterLowercase(attribute.Name.LocalName)] = attribute.Value;
+                    }
+                }
+            }
+
+            return jsonObject;
+        }
+
+        /// <summary>
+        /// Creates a JsonObject from an XML container element with each attribute or subelement as a property
+        /// </summary>
+        /// <param name="container">The XML container</param>
+        /// <param name="buildValue">Function that builds a value from a property element</param>
+        /// <returns>The JsonObject containing the name-value pairs</returns>
+        public static Dictionary<string, object> BuildElementJsonObject(XElement container)
+        {
+            if (container == null)
+            {
+                return null;
+            }
+
+            Dictionary<string, object> jsonObject = new Dictionary<string, object>();
+            string keyName = MakeFirstLetterLowercase(container.Name.LocalName);
+
+            if (container.HasAttributes || container.HasElements)
+            {
+                Dictionary<string, List<Dictionary<string, object>>> repeatingObjectArrays = new Dictionary<string, List<Dictionary<string, object>>>();
+
+                jsonObject = BuildAttributeJsonObject(container.Attributes());
+
+                foreach (XElement propertyElement in container.Elements())
+                {
+                    string propertyName = MakeFirstLetterLowercase(propertyElement.Name.LocalName);
+                    string properyNamespace = propertyElement.Name.Namespace.ToString();
+
+                    if (string.IsNullOrEmpty(properyNamespace) || properyNamespace.StartsWith(knownNamespace, StringComparison.InvariantCultureIgnoreCase))
+                    {
+                        // Check to see if the element is repeating and needs to be an array
+                        if (repeatingElements.Contains(propertyElement.Name.LocalName))
+                        {
+                            // See if property was already created as an array, if not then create it
+                            if (!repeatingObjectArrays.ContainsKey(propertyName))
+                            {
+                                repeatingObjectArrays.Add(propertyName, new List<Dictionary<string, object>>());
+                            }
+
+                            repeatingObjectArrays[propertyName].Add(BuildElementJsonObject(propertyElement));
+                        }
+                        else
+                        {
+                            jsonObject[propertyName] = BuildElementJsonObject(propertyElement);
+                        }
+                    }
+                }
+
+                foreach (string key in repeatingObjectArrays.Keys)
+                {
+                    jsonObject[key] = repeatingObjectArrays[key].ToArray();
+                }
+            }
+            else
+            {
+                jsonObject[MakeFirstLetterLowercase(container.Name.LocalName)] = container.Value;
+            }
+
+            return jsonObject;
+        }
+
+        /// <summary>
+        /// Makes the first letter of a string lowercase
+        /// </summary>
+        /// <param name="name">The string to be modified</param>
+        /// <returns>Modified string</returns>
+        private static string MakeFirstLetterLowercase(string str)
+        {
+            if (!string.IsNullOrWhiteSpace(str))
+            {
+                if (str.Length > 1 && !(str[1].ToString() == str[1].ToString().ToUpper()))
+                {
+                    return str[0].ToString().ToLower() + str.Substring(1);
+                }
+                else
+                {
+                    return str;
+                }
+            }
+
+            return str;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/code/jsdate.cs
----------------------------------------------------------------------
diff --git a/datajs/tests/code/jsdate.cs b/datajs/tests/code/jsdate.cs
new file mode 100644
index 0000000..a02c69f
--- /dev/null
+++ b/datajs/tests/code/jsdate.cs
@@ -0,0 +1,40 @@
+/// <summary>
+/// The oracle's representation of a Javascript date object as deserialized by the library
+/// </summary>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.Net;
+    using System.Runtime.Serialization;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Syndication;
+    using System.ServiceModel.Web;
+    using System.Xml;
+    using System.Xml.Linq;
+    using Microsoft.Spatial;
+    using Microsoft.OData.Core;
+
+    [Serializable]
+    public class JsDate : JsonObject
+    {
+        private static readonly DateTime JsEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+        public JsDate(DateTime dateTime)
+            : base()
+        {
+            this["milliseconds"] = dateTime.Subtract(JsEpoch).TotalMilliseconds;
+        }
+
+        public JsDate(DateTimeOffset dateTimeOffset)
+            : this(dateTimeOffset.UtcDateTime)
+        {
+            this["__edmType"] = "Edm.DateTimeOffset";
+            this["__offset"] = (dateTimeOffset.Offset < TimeSpan.Zero ? "-" : "+") + dateTimeOffset.Offset.ToString("hh':'mm");
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/code/jsonobject.cs
----------------------------------------------------------------------
diff --git a/datajs/tests/code/jsonobject.cs b/datajs/tests/code/jsonobject.cs
new file mode 100644
index 0000000..79b914e
--- /dev/null
+++ b/datajs/tests/code/jsonobject.cs
@@ -0,0 +1,80 @@
+/// <summary>
+/// A weakly typed representation of a JSON object using a dictionary implementation
+/// </summary>
+/// <typeparam name="T">The CLR type of the values of the properties</typeparam>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Runtime.Serialization;
+
+    [Serializable]
+    [KnownType(typeof(JsonObject))]
+    [KnownType(typeof(JsonObject[]))]
+    [KnownType(typeof(JsDate))]
+    [KnownType(typeof(List<object>))]
+    public class JsonObject : ISerializable, IEnumerable<KeyValuePair<string, object>>
+    {
+        Dictionary<string, object> dictionary = new Dictionary<string, object>();
+
+        public void Remove(string key)
+        {
+            dictionary.Remove(key);
+        }
+
+        public object this[string key]
+        {
+            get
+            {
+                return this.dictionary[key];
+            }
+            set
+            {
+                this.dictionary[key] = value;
+            }
+        }
+
+        public bool ContainsKey(string key)
+        {
+            return this.dictionary.ContainsKey(key);
+        }
+
+        public static JsonObject Merge(JsonObject first, JsonObject second)
+        {
+            if (first == null)
+            {
+                return second;
+            }
+
+            if (second != null)
+            {
+                JsonObject merged = new JsonObject();
+                merged.dictionary = new Dictionary<string, object>(first.dictionary);
+                foreach (var pair in second.dictionary)
+                {
+                    merged.dictionary[pair.Key] = pair.Value;
+                }
+                return merged;
+            }
+            return first;
+        }
+
+        public void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            this.dictionary.ToList().ForEach(pair => info.AddValue(pair.Key, pair.Value));
+        }
+
+        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+        {
+            return this.dictionary.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return this.dictionary.GetEnumerator();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/code/readerutils.cs
----------------------------------------------------------------------
diff --git a/datajs/tests/code/readerutils.cs b/datajs/tests/code/readerutils.cs
new file mode 100644
index 0000000..284ce04
--- /dev/null
+++ b/datajs/tests/code/readerutils.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Web.Script.Serialization;
+
+namespace DataJS.Tests
+{
+    public static class ReaderUtils
+    {
+        public static JsonObject CreateEntryPropertyMetadata(string type)
+        {
+            return CreateEntryPropertyMetadata(type, true);
+        }
+
+        public static JsonObject CreateEntryPropertyMetadata(string type, bool withExtensions)
+        {
+            JsonObject json = new JsonObject();
+            json["type"] = type;
+
+
+            // TODO: add proper support for property extensions
+            if (withExtensions)
+            {
+                json["extensions"] = new JsonObject[] { };
+            }
+
+            return json;
+        }
+
+        public static JsonObject CreateExtension(string name, string nameSpace, string value)
+        {
+            JsonObject json = new JsonObject();
+            json["name"] = name;
+            json["namespaceURI"] = nameSpace;
+            json["value"] = value;
+            return json;
+        }
+
+        public static WebRequest CreateRequest(string url, string user = null, string password = null)
+        {
+            WebRequest request = WebRequest.Create(url);
+            if (user != null || password != null)
+            {
+                request.Credentials = new NetworkCredential(user, password);
+                request.PreAuthenticate = true;
+            }
+
+            return request;
+        }
+
+        public static Stream ConvertDictionarytoJsonlightStream(Dictionary<string, object> dict)
+        {
+            MemoryStream stream = new MemoryStream();
+            if (dict == null)
+            {
+                return stream;
+            }
+
+            string jsonString = new JavaScriptSerializer().Serialize(dict);
+            StreamWriter writer = new StreamWriter(stream);
+            writer.Write(jsonString);
+            writer.Flush();
+            stream.Position = 0;
+            return stream;
+        }
+
+    }
+}
\ No newline at end of file


[05/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-json-light-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-json-light-tests.js b/datajs/tests/odata-json-light-tests.js
new file mode 100644
index 0000000..a00e941
--- /dev/null
+++ b/datajs/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 jsonLightUrisShould

<TRUNCATED>

[08/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-atom-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-atom-tests.js b/datajs/tests/odata-atom-tests.js
new file mode 100644
index 0000000..7131e1e
--- /dev/null
+++ b/datajs/tests/odata-atom-tests.js
@@ -0,0 +1,4745 @@
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/odata-handler.js" />
+/// <reference path="../src/odata-atom.js" />
+/// <reference path="../src/odata-xml.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.
+
+// odata-atom-tests.js
+
+(function (window, undefined) {
+
+    module("Unit");
+
+    var parseMetadataHelper = function (text) {
+        var response = { statusCode: 200, body: text, headers: { "Content-Type": "application/xml"} };
+        OData.metadataHandler.read(response, {});
+        return response.data;
+    };
+
+    var resetFoodData = function () {
+        $.ajax({ url: "./endpoints/FoodStoreDataServiceV4.svc/ResetData", async: false, type: "POST" });
+    };
+
+    var customerSampleMetadataText = '' +
+    '<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="Ns" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">\r\n' +
+    '    <EntityType Name="Customer">\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="true" m:FC_TargetPath="SyndicationSummary" m:FC_ContentKind="xhtml" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="LastName" Type="Edm.String" Nullable="true" m:FC_TargetPath="foo/bar/@baz" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="FavoriteNumber" Type="Edm.Int32" Nullable="true" m:FC_TargetPath="favorite/number" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="Address" Type="Ns.Address" Nullable="false" \r\n' +
+    '       m:FC_TargetPath="foo/bar/@city" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_SourcePath="City" m:FC_KeepInContent="false" \r\n' +
+    '       m:FC_TargetPath_1="foo/bar" m:FC_NsUri_1="htp://prefix" m:FC_NsPrefix_1="prefix" m:FC_SourcePath_1="Street" m:FC_KeepInContent_1="false" />\r\n' +
+    '    </EntityType>\r\n' +
+    '    <ComplexType Name="Address">\r\n' +
+    '     <Property Name="Street" Type="Edm.String" Nullable="true" />\r\n' +
+    '     <Property Name="City" Type="Edm.String" Nullable="true" />\r\n' +
+    '    </ComplexType>\r\n' +
+    '    <EntityContainer Name="SampleContext" m:IsDefaultEntityContainer="true">\r\n' +
+    '     <EntitySet Name="Customers" EntityType="Ns.Customer" />\r\n' +
+    '    </EntityContainer>\r\n' +
+    '</Schema>\r\n' +
+    '</edmx:DataServices></edmx:Edmx>';
+
+    var foodServiceV4FoodsSampleText = '' +
+    '<feed xml:base="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Foods">' +
+	'<id>http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Foods</id>' +
+	'<title type="text">Foods</title>' +
+	'<updated>2013-12-30T05:45:07Z</updated>' +
+	'<link rel="self" title="Foods" href="Foods" />' +
+	'<entry>' +
+	'	<id>http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Foods(0)</id>' +
+	'	<category term="#DataJS.Tests.V4.Food" scheme="http://docs.oasis-open.org/odata/ns/scheme" />' +
+	'	<link rel="edit" title="Food" href="Foods(0)" />' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+	'	<title />' +
+	'	<updated>2013-12-30T05:45:07Z</updated>' +
+	'	<author>' +
+	'		<name />' +
+	'	</author>' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/mediaresource/Picture" type="image/png" title="Picture" href="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Picture" />' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/edit-media/Picture" type="image/png" title="Picture" href="Foods(0)/Picture" m:etag="W/&quot;123456789&quot;" />' +
+	'	<content type="application/xml">' +
+	'		<m:properties>' +
+	'			<d:FoodID m:type="Int32">0</d:FoodID>' +
+	'			<d:Name>flour</d:Name>' +
+	'			<d:UnitPrice m:type="Double">0.19999</d:UnitPrice>' +
+	'			<d:ServingSize m:type="Decimal">1</d:ServingSize>' +
+	'			<d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+	'			<d:ProteinGrams m:type="Byte">3</d:ProteinGrams>' +
+	'			<d:FatGrams m:type="Int16">1</d:FatGrams>' +
+	'			<d:CarbohydrateGrams m:type="Int32">20</d:CarbohydrateGrams>' +
+	'			<d:CaloriesPerServing m:type="Int64">140</d:CaloriesPerServing>' +
+	'			<d:IsAvailable m:type="Boolean">true</d:IsAvailable>' +
+	'			<d:ExpirationDate m:type="DateTimeOffset">2010-12-25T12:00:00Z</d:ExpirationDate>' +
+	'			<d:ItemGUID m:type="Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+	'			<d:Weight m:type="Single">10</d:Weight>' +
+	'			<d:AvailableUnits m:type="SByte">1</d:AvailableUnits>' +
+	'			<d:Packaging m:type="#DataJS.Tests.V4.Package">' +
+	'				<d:Type m:null="true" />' +
+	'				<d:Color></d:Color>' +
+	'				<d:NumberPerPackage m:type="Int32">2147483647</d:NumberPerPackage>' +
+	'				<d:RequiresRefridgeration m:type="Boolean">false</d:RequiresRefridgeration>' +
+	'				<d:ShipDate m:type="DateTimeOffset">2000-12-29T00:00:00Z</d:ShipDate>' +
+	'				<d:PackageDimensions m:type="#DataJS.Tests.V4.Dimensions">' +
+	'					<d:Length m:type="Decimal">79228162514264337593543950335</d:Length>' +
+	'					<d:Height m:type="Int16">32767</d:Height>' +
+	'					<d:Width m:type="Int64">9223372036854775807</d:Width>' +
+	'					<d:Volume m:type="Double">1.7976931348623157E+308</d:Volume>' +
+	'				</d:PackageDimensions>' +
+	'			</d:Packaging>' +
+	'			<d:CookedSize m:type="#DataJS.Tests.V4.CookedDimensions">' +
+	'				<d:Length m:type="Decimal">2</d:Length>' +
+	'				<d:Height m:type="Int16">1</d:Height>' +
+	'				<d:Width m:type="Int64">3</d:Width>' +
+	'				<d:Volume m:type="Double">6</d:Volume>' +
+	'			</d:CookedSize>' +
+	'			<d:AlternativeNames m:type="#Collection(String)">' +
+	'				<m:element>ground cereal</m:element>' +
+	'				<m:element>ground grain</m:element>' +
+	'			</d:AlternativeNames>' +
+	'			<d:Providers m:type="#Collection(DataJS.Tests.V4.Provider)">' +
+	'				<m:element>' +
+	'					<d:Name>Flour Provider</d:Name>' +
+	'					<d:Aliases m:type="#Collection(String)">' +
+	'						<m:element>fp1</m:element>' +
+	'						<m:element>flour provider1</m:element>' +
+	'					</d:Aliases>' +
+	'					<d:Details m:type="#DataJS.Tests.V4.ProviderDetails">' +
+	'						<d:Telephone>555-555-555</d:Telephone>' +
+	'						<d:PreferredCode m:type="Int32">1001</d:PreferredCode>' +
+	'					</d:Details>' +
+	'				</m:element>' +
+	'				<m:element>' +
+	'					<d:Name>Ground Grains</d:Name>' +
+	'					<d:Aliases m:type="#Collection(String)" />' +
+	'					<d:Details m:null="true" />' +
+	'				</m:element>' +
+	'			</d:Providers>' +
+	'			<d:SpatialData m:type="GeometryCollection">' +
+	'				<gml:MultiGeometry gml:srsName="http://www.opengis.net/def/crs/EPSG/0/4326">' +
+	'					<gml:geometryMembers>' +
+	'						<gml:Point>' +
+	'							<gml:pos>5 5</gml:pos>' +
+	'						</gml:Point>' +
+	'					</gml:geometryMembers>' +
+	'				</gml:MultiGeometry>' +
+	'			</d:SpatialData>' +
+	'		</m:properties>' +
+	'	</content>' +
+	'</entry>' +
+	'<entry>' +
+	'	<id>http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Foods(1)</id>' +
+	'	<category term="#DataJS.Tests.V4.Food" scheme="http://docs.oasis-open.org/odata/ns/scheme" />' +
+	'	<link rel="edit" title="Food" href="Foods(1)" />' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(1)/Category" />' +
+	'	<title />' +
+	'	<updated>2013-12-30T05:45:07Z</updated>' +
+	'	<author>' +
+	'		<name />' +
+	'	</author>' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/mediaresource/Picture" type="image/png" title="Picture" href="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Picture" />' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/edit-media/Picture" type="image/png" title="Picture" href="Foods(1)/Picture" m:etag="W/&quot;123456789&quot;" />' +
+	'	<content type="application/xml">' +
+	'		<m:properties>' +
+	'			<d:FoodID m:type="Int32">1</d:FoodID>' +
+	'			<d:Name>sugar</d:Name>' +
+	'			<d:UnitPrice m:type="Double">0.2</d:UnitPrice>' +
+	'			<d:ServingSize m:type="Decimal">1</d:ServingSize>' +
+	'			<d:MeasurementUnit>tsp</d:MeasurementUnit>' +
+	'			<d:ProteinGrams m:type="Byte">0</d:ProteinGrams>' +
+	'			<d:FatGrams m:type="Int16">0</d:FatGrams>' +
+	'			<d:CarbohydrateGrams m:type="Int32">4</d:CarbohydrateGrams>' +
+	'			<d:CaloriesPerServing m:type="Int64">16</d:CaloriesPerServing>' +
+	'			<d:IsAvailable m:type="Boolean">false</d:IsAvailable>' +
+	'			<d:ExpirationDate m:type="DateTimeOffset">2011-12-28T00:00:00Z</d:ExpirationDate>' +
+	'			<d:ItemGUID m:type="Guid">ffffffff-ffff-ffff-ffff-ffffffffffff</d:ItemGUID>' +
+	'			<d:Weight m:type="Single">0.1</d:Weight>' +
+	'			<d:AvailableUnits m:type="SByte">0</d:AvailableUnits>' +
+	'			<d:Packaging m:type="#DataJS.Tests.V4.Package">' +
+	'				<d:Type xml:space="preserve"> </d:Type>' +
+	'				<d:Color>BLUE</d:Color>' +
+	'				<d:NumberPerPackage m:type="Int32">-2147483648</d:NumberPerPackage>' +
+	'				<d:RequiresRefridgeration m:type="Boolean">true</d:RequiresRefridgeration>' +
+	'				<d:ShipDate m:type="DateTimeOffset">2000-12-29T00:00:00Z</d:ShipDate>' +
+	'				<d:PackageDimensions m:type="#DataJS.Tests.V4.Dimensions">' +
+	'					<d:Length m:type="Decimal">-79228162514264337593543950335</d:Length>' +
+	'					<d:Height m:type="Int16">-32768</d:Height>' +
+	'					<d:Width m:type="Int64">-9223372036854775808</d:Width>' +
+	'					<d:Volume m:type="Double">-1.7976931348623157E+308</d:Volume>' +
+	'				</d:PackageDimensions>' +
+	'			</d:Packaging>' +
+	'			<d:CookedSize m:null="true" />' +
+	'			<d:AlternativeNames m:type="#Collection(String)" />' +
+	'			<d:Providers m:type="#Collection(DataJS.Tests.V4.Provider)" />' +
+	'			<d:SpatialData m:null="true" />' +
+	'		</m:properties>' +
+	'	</content>' +
+	'</entry>' +
+    '</feed>';
+
+    var foodServiceV4MetadataText = '' +
+    '<?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>\r\n' +
+	'	<Schema Namespace="DataJS.Tests.V4" xmlns="http://docs.oasis-open.org/odata/ns/edm">\r\n' +
+	'		<EntityType Name="Category">\r\n' +
+	'			<Key>\r\n' +
+	'				<PropertyRef Name="CategoryID" />\r\n' +
+	'			</Key>\r\n' +
+	'			<Property Name="Icon" Type="Edm.Stream" Nullable="false" />\r\n' +
+	'			<Property Name="CategoryID" Type="Edm.Int32" Nullable="false" />\r\n' +
+	'			<Property Name="Name" Type="Edm.String" />\r\n' +
+	'			<NavigationProperty Name="Foods" Type="Collection(DataJS.Tests.V4.Food)" Partner="Category" />\r\n' +
+	'		</EntityType>\r\n' +
+	'		<EntityType Name="Food">\r\n' +
+	'			<Key>\r\n' +
+	'				<PropertyRef Name="FoodID" />\r\n' +
+	'			</Key>\r\n' +
+	'			<Property Name="Picture" Type="Edm.Stream" Nullable="false" />\r\n' +
+	'			<Property Name="FoodID" Type="Edm.Int32" Nullable="false" />\r\n' +
+	'			<Property Name="Name" Type="Edm.String" />\r\n' +
+	'			<Property Name="UnitPrice" Type="Edm.Double" Nullable="false" />\r\n' +
+	'			<Property Name="ServingSize" Type="Edm.Decimal" Nullable="false" />\r\n' +
+	'			<Property Name="MeasurementUnit" Type="Edm.String" />\r\n' +
+	'			<Property Name="ProteinGrams" Type="Edm.Byte" Nullable="false" />\r\n' +
+	'			<Property Name="FatGrams" Type="Edm.Int16" Nullable="false" />\r\n' +
+	'			<Property Name="CarbohydrateGrams" Type="Edm.Int32" Nullable="false" />\r\n' +
+	'			<Property Name="CaloriesPerServing" Type="Edm.Int64" Nullable="false" />\r\n' +
+	'			<Property Name="IsAvailable" Type="Edm.Boolean" Nullable="false" />\r\n' +
+	'			<Property Name="ExpirationDate" Type="Edm.DateTimeOffset" Nullable="false" />\r\n' +
+	'			<Property Name="ItemGUID" Type="Edm.Guid" Nullable="false" />\r\n' +
+	'			<Property Name="Weight" Type="Edm.Single" Nullable="false" />\r\n' +
+	'			<Property Name="AvailableUnits" Type="Edm.SByte" Nullable="false" />\r\n' +
+	'			<Property Name="Packaging" Type="DataJS.Tests.V4.Package" />\r\n' +
+	'			<Property Name="CookedSize" Type="DataJS.Tests.V4.CookedDimensions" />\r\n' +
+	'			<Property Name="AlternativeNames" Type="Collection(Edm.String)" Nullable="false" />\r\n' +
+	'			<Property Name="Providers" Type="Collection(DataJS.Tests.V4.Provider)" Nullable="false" />\r\n' +
+	'			<Property Name="SpatialData" Type="Edm.GeometryCollection" SRID="Variable" />\r\n' +
+	'			<NavigationProperty Name="Category" Type="DataJS.Tests.V4.Category" Partner="Foods" />\r\n' +
+	'		</EntityType>\r\n' +
+	'		<EntityType Name="PreparedFood" BaseType="DataJS.Tests.V4.Food">\r\n' +
+	'			<Property Name="Instructions" Type="Edm.String" />\r\n' +
+	'			<Property Name="NumberOfIngredients" Type="Edm.Single" Nullable="false" />\r\n' +
+	'		</EntityType>\r\n' +
+	'		<ComplexType Name="Package">\r\n' +
+	'			<Property Name="Type" Type="Edm.String" />\r\n' +
+	'			<Property Name="Color" Type="Edm.String" />\r\n' +
+	'			<Property Name="NumberPerPackage" Type="Edm.Int32" Nullable="false" />\r\n' +
+	'			<Property Name="RequiresRefridgeration" Type="Edm.Boolean" Nullable="false" />\r\n' +
+	'			<Property Name="ShipDate" Type="Edm.DateTimeOffset" Nullable="false" />\r\n' +
+	'			<Property Name="PackageDimensions" Type="DataJS.Tests.V4.Dimensions" />\r\n' +
+	'		</ComplexType>\r\n' +
+	'		<ComplexType Name="Dimensions">\r\n' +
+	'			<Property Name="Length" Type="Edm.Decimal" Nullable="false" />\r\n' +
+	'			<Property Name="Height" Type="Edm.Int16" Nullable="false" />\r\n' +
+	'			<Property Name="Width" Type="Edm.Int64" Nullable="false" />\r\n' +
+	'			<Property Name="Volume" Type="Edm.Double" Nullable="false" />\r\n' +
+	'		</ComplexType>\r\n' +
+	'		<ComplexType Name="CookedDimensions">\r\n' +
+	'			<Property Name="Length" Type="Edm.Decimal" Nullable="false" />\r\n' +
+	'			<Property Name="Height" Type="Edm.Int16" Nullable="false" />\r\n' +
+	'			<Property Name="Width" Type="Edm.Int64" Nullable="false" />\r\n' +
+	'			<Property Name="Volume" Type="Edm.Double" Nullable="false" />\r\n' +
+	'		</ComplexType>\r\n' +
+	'		<ComplexType Name="Provider">\r\n' +
+	'			<Property Name="Name" Type="Edm.String" />\r\n' +
+	'			<Property Name="Aliases" Type="Collection(Edm.String)" Nullable="false" />\r\n' +
+	'			<Property Name="Details" Type="DataJS.Tests.V4.ProviderDetails" />\r\n' +
+	'		</ComplexType>\r\n' +
+	'		<ComplexType Name="ProviderDetails">\r\n' +
+	'			<Property Name="Telephone" Type="Edm.String" />\r\n' +
+	'			<Property Name="PreferredCode" Type="Edm.Int32" Nullable="false" />\r\n' +
+	'		</ComplexType>\r\n' +
+	'		<Action Name="ResetData">\r\n' +
+	'			<ReturnType Type="Edm.String" />\r\n' +
+	'		</Action>\r\n' +
+	'		<Function Name="FoodsAvailable" IsComposable="true">\r\n' +
+	'			<ReturnType Type="Collection(Edm.String)" />\r\n' +
+	'		</Function>\r\n' +
+	'		<Function Name="PackagingTypes" IsComposable="true">\r\n' +
+	'			<ReturnType Type="Collection(DataJS.Tests.V4.Package)" />\r\n' +
+	'		</Function>\r\n' +
+	'		<Function Name="UserNameAndPassword">\r\n' +
+	'			<ReturnType Type="Edm.String" />\r\n' +
+	'		</Function>\r\n' +
+	'		<EntityContainer Name="FoodContainer">\r\n' +
+	'			<EntitySet Name="Categories" EntityType="DataJS.Tests.V4.Category">\r\n' +
+	'				<NavigationPropertyBinding Path="Foods" Target="Foods" />\r\n' +
+	'			</EntitySet>\r\n' +
+	'			<EntitySet Name="Foods" EntityType="DataJS.Tests.V4.Food">\r\n' +
+	'				<NavigationPropertyBinding Path="Category" Target="Categories" />\r\n' +
+	'			</EntitySet>\r\n' +
+	'			<ActionImport Name="ResetData" Action="DataJS.Tests.V4.ResetData" />\r\n' +
+	'			<FunctionImport Name="FoodsAvailable" Function="DataJS.Tests.V4.FoodsAvailable" IncludeInServiceDocument="true" />\r\n' +
+	'			<FunctionImport Name="PackagingTypes" Function="DataJS.Tests.V4.PackagingTypes" IncludeInServiceDocument="true" />\r\n' +
+	'			<FunctionImport Name="UserNameAndPassword" Function="DataJS.Tests.V4.UserNameAndPassword" IncludeInServiceDocument="true" />\r\n' +
+	'		</EntityContainer>\r\n' +
+	'	</Schema>\r\n' +
+	'</edmx:DataServices>\r\n' +
+    '</edmx:Edmx>';
+
+    djstest.addFullTest(true, function applyEntryCustomizationToEntryTest() {
+        var metadata = parseMetadataHelper(customerSampleMetadataText);
+        var data = { __metadata: { type: "Ns.Customer" }, Name: "Name", LastName: "Last Name", Address: { Street: "Street Value", City: "City Value" }, FavoriteNumber: 123 };
+        var request = { data: data, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.write(request, { metadata: metadata });
+
+        djstest.assert(request.body !== null, "request.body !== null");
+        djstest.assert(request.body.indexOf("<a:summary type=\"xhtml\">Name</a:summary>") !== -1, 'request.body.indexOf("<a:summary>Name</a:summary>") !== -1');
+        djstest.assert(request.body.indexOf('baz="Last Name"') !== -1, 'request.body.indexOf(baz="Last Name") !== -1');
+        djstest.assert(request.body.indexOf('city="City Value"') !== -1, 'request.body.indexOf(city="City Value") !== -1');
+        djstest.assert(request.body.indexOf('<prefix:foo ') !== -1, "request.body.indexOf('<prefix:foo ') !== -1");
+        djstest.assert(request.body.indexOf('term="Ns.Customer"') !== -1, "request.body.indexOf(term='Ns.Customer') !== -1");
+        djstest.assert(request.body.indexOf('>123</') !== -1, "request.body.indexOf(>123</) !== -1");
+
+        // Try with other mapping types.
+        metadata.dataServices.schema[0].entityType[0].property[1].FC_ContentKind = "html";
+        request.body = undefined;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf("<a:summary type=\"html\">Name</a:summary>") !== -1, 'request.body.indexOf("<a:summary type="html">Name</a:summary>") !== -1');
+
+        // Try with a null value now.
+        request.data.FavoriteNumber = null;
+        request.body = null;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf('>123</') === -1, "request.body.indexOf(>123</) === -1");
+        djstest.assert(request.body.indexOf('m:null="true"') !== -1, "request.body.indexOf(m:null=true) !== -1");
+
+        // Try with a null complex type now.
+        request.data.FavoriteNumber = 123;
+        request.data.Address = null;
+        request.body = null;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf('Street') === -1, "request.body.indexOf(Street) === -1");
+        djstest.assert(request.body.indexOf('m:null="true"') !== -1, "request.body.indexOf(m:null=true) !== -1");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testParsePrimitivePropertiesBasic() {
+        var feed = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:d=\'http://schemas.microsoft.com/ado/2007/08/dataservices\' \r\n\
+               xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'>\r\n\
+           <id>http://services.odata.org/OData/OData.svc/the id</id> \r\n \
+           <content type='application/xml'>\r\n \
+            <m:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n \
+             <Boolean m:type='Edm.Boolean'>true</Boolean>\r\n \
+             <Binary m:type='Edm.Binary'>01007A8A680D9E14A64EAC1242DD33C9DB05</Binary>\r\n \
+             <Byte m:type='Edm.Byte'>8</Byte>\r\n \
+             <DateTime m:type='Edm.DateTime'>2010-11-01T15:13:25</DateTime>\r\n \
+             <Decimal m:type='Edm.Decimal'>100.10</Decimal>\r\n \
+             <Guid m:type='Edm.Guid'>12345678-aaaa-bbbb-cccc-ddddeeeeffff</Guid>\r\n \
+             <!-- <Time m:type='Edm.Time'>P05DT12H30M05.125S</Time> --> \r\n \
+             <DateTimeOffset m:type='Edm.DateTimeOffset'>2010-11-01T15:13:25+10:00</DateTimeOffset>\r\n \
+             <Double m:type='Edm.Double'>1E+10</Double>\r\n \
+             <Single m:type='Edm.Single'>100.01</Single>\r\n \
+             <Int16 m:type='Edm.Int16'>16</Int16>\r\n \
+             <Int32 m:type='Edm.Int32'>32</Int32>\r\n \
+             <Int64 m:type='Edm.Int64'>64</Int64>\r\n \
+             <SByte m:type='Edm.SByte'>-8</SByte>\r\n \
+            </m:properties>\r\n \
+           </content>\r\n \
+        </entry>\r\n";
+
+        var response = { body: feed, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, {});
+
+        djstest.assertsExpected(1);
+        ODataReadOracle.readEntryLoopback(feed, function (expectedData) {
+            djstest.assertAreEqualDeep(response.data, expectedData, "Verify deserialized data");
+            djstest.done();
+        });
+    });
+
+    djstest.addFullTest(true, function deserializeCustomizationsNullAndXhtmlTest() {
+        var payload = "<entry " +
+        ' xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" \r\n' +
+        " xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n" +
+        " xmlns=\"http://www.w3.org/2005/Atom\">\r\n" +
+        " <id>http://localhost/tests/endpoints/FoodDataService.svc/Foods(1)</id> " +
+        " <author><name>Customer #1</name></author>" +
+        " <summary><b>Name</b></summary>" +
+        " <category term='Ns.Customer' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' /> " +
+        " <content type='application/xml'><m:properties><d:ID m:type='Edm.Int32'>1</d:ID>" +
+        " <d:LastName m:null='true' /></m:properties></content>" +
+        "</entry>";
+        var metadata = parseMetadataHelper(customerSampleMetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assertAreEqual(response.data.LastName, null, "last name is null");
+        djstest.assertAreEqual(response.data.Name, "<b xmlns=\"http://www.w3.org/2005/Atom\">Name</b>", "name includes tags");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function parseCustomizationSampleTest() {
+        var payload = foodServiceV4FoodsSampleText;
+        var metadata = parseMetadataHelper(foodServiceV4MetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assert(response.data !== null, "response.data !== null");
+        djstest.assert(response.data.results !== null, "response.data.results !== null");
+
+        var r = response.data.results;
+        djstest.assertAreEqual(r[0].__metadata.type, "DataJS.Tests.V4.Food", "r[0].__metadata.type");
+        djstest.assertAreEqual(r[0].Name, "flour", "r[0].Name");
+        djstest.assertAreEqual(r[0].UnitPrice, 0.19999, "r[0].UnitPrice");
+        djstest.assertAreEqual(r[0].ServingSize, 1, "r[0].ServingSize");
+
+        // CONSIDER: we intended to have complex type have their type annotation out-of-band, but JSON has it in-line; do we want to normalize this out everywhere?
+        // djstest.assertAreEqual(r[0].Packaging.__metadata.type, "DataJS.Tests.PackageV4", "r[0].Packaging.__metadata.type");
+        djstest.assertAreEqual(r[0].Packaging.Type, null, "package type for flour is null");
+        djstest.assertAreEqual(r[0].Packaging.PackageDimensions.Height, 32767, "r[0].Packaging.PackageDimensions.Height");
+
+        djstest.assertAreEqual(r[0].CookedSize.Length, 2, "r[0].CookedSize.Length");
+        djstest.assertAreEqual(r[0].CookedSize.Volume, 6, "r[0].CookedSize.Volume");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function parseIntoPropertiesTest() {
+        var payload = '' +
+    '<entry xml:base="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Foods/$entity">' +
+	'<id>http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Foods(0)</id>' +
+	'<category term="#DataJS.Tests.V4.Food" scheme="http://docs.oasis-open.org/odata/ns/scheme" />' +
+	'<link rel="edit" title="Food" href="Foods(0)" />' +
+	'<link rel="http://docs.oasis-open.org/odata/ns/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+	'<title />' +
+	'<updated>2013-12-30T06:01:30Z</updated>' +
+	'<author>' +
+	'	<name />' +
+	'</author>' +
+	'<link rel="http://docs.oasis-open.org/odata/ns/mediaresource/Picture" type="image/png" title="Picture" href="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Picture" />' +
+	'<link rel="http://docs.oasis-open.org/odata/ns/edit-media/Picture" type="image/png" title="Picture" href="Foods(0)/Picture" m:etag="W/&quot;123456789&quot;" />' +
+	'<content type="application/xml">' +
+	'	<m:properties>' +
+	'		<d:FoodID m:type="Int32">0</d:FoodID>' +
+	'		<d:Name>flour</d:Name>' +
+	'		<d:UnitPrice m:type="Double">0.19999</d:UnitPrice>' +
+	'		<d:ServingSize m:type="Decimal">1</d:ServingSize>' +
+	'		<d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+	'		<d:ProteinGrams m:type="Byte">3</d:ProteinGrams>' +
+	'		<d:FatGrams m:type="Int16">1</d:FatGrams>' +
+	'		<d:CarbohydrateGrams m:type="Int32">20</d:CarbohydrateGrams>' +
+	'		<d:CaloriesPerServing m:type="Int64">140</d:CaloriesPerServing>' +
+	'		<d:IsAvailable m:type="Boolean">true</d:IsAvailable>' +
+	'		<d:ExpirationDate m:type="DateTimeOffset">2010-12-25T12:00:00Z</d:ExpirationDate>' +
+	'		<d:ItemGUID m:type="Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+	'		<d:Weight m:type="Single">10</d:Weight>' +
+	'		<d:AvailableUnits m:type="SByte">1</d:AvailableUnits>' +
+	'		<d:Packaging m:type="#DataJS.Tests.V4.Package">' +
+	'			<d:Type m:null="true" />' +
+	'			<d:Color></d:Color>' +
+	'			<d:NumberPerPackage m:type="Int32">2147483647</d:NumberPerPackage>' +
+	'			<d:RequiresRefridgeration m:type="Boolean">false</d:RequiresRefridgeration>' +
+	'			<d:ShipDate m:type="DateTimeOffset">2000-12-29T00:00:00Z</d:ShipDate>' +
+	'			<d:PackageDimensions m:type="#DataJS.Tests.V4.Dimensions">' +
+	'				<d:Length m:type="Decimal">79228162514264337593543950335</d:Length>' +
+	'				<d:Height m:type="Int16">32767</d:Height>' +
+	'				<d:Width m:type="Int64">9223372036854775807</d:Width>' +
+	'				<d:Volume m:type="Double">1.7976931348623157E+308</d:Volume>' +
+	'			</d:PackageDimensions' +
+	'		</d:Packaging>' +
+	'		<d:CookedSize m:type="#DataJS.Tests.V4.CookedDimensions">' +
+	'			<d:Length m:type="Decimal">2</d:Length>' +
+	'			<d:Height m:type="Int16">1</d:Height>' +
+	'			<d:Width m:type="Int64">3</d:Width>' +
+	'			<d:Volume m:type="Double">6</d:Volume>' +
+	'		</d:CookedSize>' +
+	'		<d:AlternativeNames m:type="#Collection(String)">' +
+	'			<m:element>ground cereal</m:element>' +
+	'			<m:element>ground grain</m:element>' +
+	'		</d:AlternativeNames>' +
+	'		<d:Providers m:type="#Collection(DataJS.Tests.V4.Provider)">' +
+	'			<m:element>' +
+	'				<d:Name>Flour Provider</d:Name>' +
+	'				<d:Aliases m:type="#Collection(String)">' +
+	'					<m:element>fp1</m:element>' +
+	'					<m:element>flour provider1</m:element>' +
+	'				</d:Aliases' +
+	'				<d:Details m:type="#DataJS.Tests.V4.ProviderDetails">' +
+	'					<d:Telephone>555-555-555</d:Telephone>' +
+	'					<d:PreferredCode m:type="Int32">1001</d:PreferredCode>' +
+	'				</d:Details>' +
+	'			</m:element>' +
+	'			<m:element>' +
+	'				<d:Name>Ground Grains</d:Name>' +
+	'				<d:Aliases m:type="#Collection(String)" />' +
+	'				<d:Details m:null="true" />' +
+	'			</m:element>' +
+	'		</d:Providers>' +
+	'		<d:SpatialData m:type="GeometryCollection">' +
+	'			<gml:MultiGeometry gml:srsName="http://www.opengis.net/def/crs/EPSG/0/4326">' +
+	'				<gml:geometryMembers>' +
+	'					<gml:Point>' +
+	'						<gml:pos>5 5</gml:pos>' +
+	'					</gml:Point>' +
+	'				</gml:geometryMembers>' +
+	'			</gml:MultiGeometry>' +
+	'		</d:SpatialData>' +
+	'	</m:properties>' +
+	'</content>' +
+    '</entry>';
+
+        var metadata = parseMetadataHelper(foodServiceV4MetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assert(response.data !== null, "response.data !== null");
+        djstest.assertAreEqual(response.data.__metadata.type, "DataJS.Tests.V4.Food", "types match");
+        djstest.assertAreEqual(response.data.UnitPrice, 0.19999, "Price is as expected");
+        djstest.assertAreEqual(response.data.__metadata.properties.UnitPrice.type, "Edm.Double", "Price was marked as a double");
+
+        djstest.assertAreEqual(response.data.CookedSize.__metadata.properties.Length.type, "Edm.Decimal", "CookedSize.Length was marked as a decimal");
+
+        // When properties are marked on complex type metadata, this assertion will be true as well.
+        // djstest.assertAreEqual(response.data.Packaging.__metadata.Type.type, "Edm.String", "Packaging type was marked as a string");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function parseNullInlineTest() {
+        // Shorter version of:
+        // OData.read("/tests/endpoints/FoodStoreDataService.svc/Foods?$expand=Category&$filter=Category eq null", function(data, response) {
+
+        var body = '' +
+    '<feed xml:base="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/$metadata#Foods">' +
+	'<id>http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Foods</id>' +
+	'<title type="text">Foods</title>' +
+	'<updated>2013-12-30T06:09:18Z</updated>' +
+	'<link rel="self" title="Foods" href="Foods" />' +
+	'<entry>' +
+	'	<id>http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Foods(2)</id>' +
+	'	<category term="#DataJS.Tests.V4.Food" scheme="http://docs.oasis-open.org/odata/ns/scheme" />' +
+	'	<link rel="edit" title="Food" href="Foods(2)" />' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(2)/Category">' +
+	'		<m:inline />' +
+	'	</link>' +
+	'	<title />' +
+	'	<updated>2013-12-30T06:09:18Z</updated>' +
+	'	<author>' +
+	'		<name />' +
+	'	</author>' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/mediaresource/Picture" type="image/png" title="Picture" href="http://localhost:46541/tests/endpoints/FoodStoreDataServiceV4.svc/Picture" />' +
+	'	<link rel="http://docs.oasis-open.org/odata/ns/edit-media/Picture" type="image/png" title="Picture" href="Foods(2)/Picture" m:etag="W/&quot;123456789&quot;" />' +
+	'	<content type="application/xml">' +
+	'		<m:properties>' +
+	'			<d:FoodID m:type="Int32">2</d:FoodID>' +
+	'			<d:Name>1 Chicken Egg</d:Name>' +
+	'			<d:UnitPrice m:type="Double">0.55</d:UnitPrice>' +
+	'			<d:ServingSize m:type="Decimal">1</d:ServingSize>' +
+	'			<d:MeasurementUnit m:null="true" />' +
+	'			<d:ProteinGrams m:type="Byte">6</d:ProteinGrams>' +
+	'		</m:properties>' +
+	'	</content>' +
+	'</entry>' +
+    '</feed>';
+        var response = { body: body, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, {});
+        var data = response.data;
+        var r = data.results[0];
+        djstest.assertAreEqual(r.Category, null, "r.Category is null as an empty inline entry");
+        djstest.assertAreEqual(r.FoodID, 2, "r.FoodID read correctly");
+        djstest.assertAreEqual(r.__metadata.properties.Category.extensions[0].name, "title", "Category title extension parsed");
+        djstest.assertAreEqual(r.__metadata.properties.Category.extensions[0].value, "Category", "Category title value parsed");
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function serializeEpmTest() {
+        var metadata = parseMetadataHelper(foodServiceV4MetadataText);
+        var data = { __metadata: { type: "DataJS.Tests.V4.Food" }, FoodID: 123, Name: "name", CookedSize: { Length: 1, Height: 2, Width: 3, Volume: 4} };
+        var request = { data: data, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf("CookedSize") === -1, "CookedSize element is missing from payload");
+        djstest.assert(request.body.indexOf("length=") !== -1, "length is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("height=") !== -1, "height is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("width=") !== -1, "width is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("volume>") !== -1, "volume is available as a mapped element");
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function writeNullComplexTypeTest() {
+
+        // Verify that the server can be updated to set a complex value to null.
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV4.svc/Foods";
+        resetFoodData();
+
+        // Without metadata, this will fail because the server version won't be set to 2.0.
+        var metadata = parseMetadataHelper(foodServiceV4MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+            djstest.assert(item.Packaging, "item.Packaging is not null");
+            item.Packaging = null;
+
+            // The server will reject links for PUT operations.
+            delete item.Category;
+
+            OData.request({ method: "PUT", requestUri: item.__metadata.uri, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(item.__metadata.uri, function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(!data.Packaging, "!data.Packaging");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to write food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    djstest.addFullTest(true, function writeNonNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV4.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV4MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+
+            // Turn this into something different.
+            delete item.__metadata.uri;
+            item.FoodID = 1001;
+
+            OData.request({ method: "POST", requestUri: foodSetUrl, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(data.__metadata.uri + "?$expand=Category", function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(data.Category, "data.Category");
+                    djstest.assert(data.Category.Name, "data.Category.Name");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to add modified food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    djstest.addFullTest(true, function writeNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV4.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV4MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+
+            // Turn this into something different.
+            delete item.__metadata.uri;
+            item.FoodID = 1001;
+            item.Category = null;
+
+            OData.request({ method: "POST", requestUri: foodSetUrl, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(data.__metadata.uri + "?$expand=Category", function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(!data.Category, "data.Category");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to add modified food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    // DATAJS INTERNAL START
+    djstest.addFullTest(true, function lookupEntityTypeInSchemaTest() {
+        var schemaEmpty = {};
+        var schemaZero = {
+            namespace: "Zero",
+            entityType: [
+                { name: "Genre" },
+                { name: "Language" }
+            ]
+        };
+        var schemaOne = {
+            namespace: "One",
+            entityType: [
+                { name: "Genre1" },
+                { name: "Language1" }
+            ]
+        };
+        var edmx = { dataServices: { schema: [schemaEmpty, schemaZero, schemaOne]} };
+
+        var lookupEntityTypeInSchema = function (name, schema) {
+            return OData.lookupInMetadata(name, schema, "entityType");
+        };
+
+        djstest.assertAreEqual(
+        lookupEntityTypeInSchema("Zero.Genre"),
+        null, "Expected null for missing metadata");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("", schemaEmpty),
+            null, "Expected null for empty type name");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("FooWar", schemaEmpty),
+            null, "Expected null for mismatched name/namespace");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero", schemaZero),
+            null, "Expected null for unqualified type name");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero.Something", schemaZero),
+            null, "Expected null for mismatched type name");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("Zero.Genre", schemaZero),
+            { name: "Genre" }, "Found type by full name");
+
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero.Something", edmx),
+            null, "Expected null for mismatched type name in edmx");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("Zero.Genre", edmx),
+            { name: "Genre" }, "Found type by full name in edmx");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("One.Genre1", edmx),
+            { name: "Genre1" }, "Found type by full name in edmx");
+
+        djstest.assertAreEqual(
+            OData.lookupInMetadata("One.Genre1", edmx, "complexType"),
+            null, "Expected null for a complex type lookup of an entity type.");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testLookupEntityType() {
+        var schemaZero = {
+            namespace: "Zero",
+            entityType: [
+                { name: "Genre" },
+                { name: "Language" }
+            ]
+        };
+        var schemaOne = {
+            namespace: "One",
+            entityType: [
+                { name: "Genre1" },
+                { name: "Language1" }
+            ]
+        };
+        var schemaTwo = {
+            namespace: "Two",
+            entityType: [
+                { name: "Genre2" },
+                { name: "Language2" }
+            ]
+        };
+        var edmx = { dataServices: { schema: [schemaZero, schemaOne]} };
+        var metadata = [edmx, schemaTwo];
+
+        djstest.assertAreEqual(
+            OData.lookupEntityType("Zero.Something", metadata),
+            null, "Expected null for mismatched type name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Zero.Genre", null),
+            null, "Expected null for missing metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType(null, metadata),
+            null, "Expected null for missing name");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Zero.Genre", metadata),
+            { name: "Genre" }, "Found type by full name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("One.Genre1", metadata),
+            { name: "Genre1" }, "Found type by full name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("One.Genre1", edmx),
+            { name: "Genre1" }, "Found type by full name in edmx");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Two.Genre2", metadata),
+            { name: "Genre2" }, "Found type by full name in metadata");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testParseSimpleServiceDocument() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Products", expectedTitle: "Products" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Categories", expectedTitle: "Categories" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Suppliers", expectedTitle: "Suppliers" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testServiceDocMustHaveAtLeastOneWorkspaceElement() {
+        // Construct a service doc with no workspaces and verify that the parser throws.
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with no workspaces");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testServiceDocMayHaveMoreThanOneWorkspaceElement() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+            <workspace>\r\n\
+              <atom:title>Second Workspace</atom:title> \r\n\
+              <collection href=\"Collection1\">\r\n\
+                <atom:title>Collection Number 1</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Collection2\">\r\n\
+                <atom:title>Collection Number 2</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 2, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections;
+        expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Products", expectedTitle: "Products" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Categories", expectedTitle: "Categories" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Suppliers", expectedTitle: "Suppliers" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        workspace = serviceDoc.workspaces[1];
+        djstest.assertAreEqual(workspace.title, "Second Workspace", "Incorrect service doc title");
+
+        expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Collection1", expectedTitle: "Collection Number 1" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Collection2", expectedTitle: "Collection Number 2" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testCollectionTitlesAndHrefsMayBeDifferent() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"abc\">\r\n\
+                <atom:title>xyz</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"blah\">\r\n\
+                <atom:title>foo</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/abc", expectedTitle: "xyz" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/blah", expectedTitle: "foo" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testParserShouldTreatMissingWorkspaceTitleAsBlank() {
+        // Per RFC 5023 Section 8.3.2.1, the workspace element MUST have a title but
+        // in the interests of being permissive, we should treat this as blank.
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <!-- No workspace title element -->\r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "", "Incorrect service doc title");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testWorkspaceMayHaveNoCollections() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        var expectedCollections = [];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testCollectionMustHaveTitleElement() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <!-- No title element -->\r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with a collection with no title element");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function testCollectionMustHaveHrefAttribute() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <!-- href attribute missing below --> \r\n\
+              <collection>\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with a collection with no href attribute");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadDocumentTest() {
+        var emptyServiceString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+          <workspace>\r\n\
+            <atom:title>empty service</atom:title> \r\n\
+          </workspace>\r\n\
+        </service>\r\n";
+
+        var emptyFeedString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>empty feed</title> \r\n\
+        </feed> \r\n";
+
+        var emptyEntryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>entry id</id> \r\n\
+           <title>empty entry</title> \r\n\
+        </entry> \r\n";
+
+        var nonAtomString = "\
+        <notAtom xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>entry id</id> \r\n\
+           <title>empty entry</title> \r\n\
+        </notAtom> \r\n";
+
+        var service = OData.atomReadDocument(datajs.xmlParse(emptyServiceString).documentElement);
+        var feed = OData.atomReadDocument(datajs.xmlParse(emptyFeedString).documentElement);
+        var entry = OData.atomReadDocument(datajs.xmlParse(emptyEntryString).documentElement);
+        var nonAtom = OData.atomReadDocument(datajs.xmlParse(nonAtomString).documentElement);
+
+        djstest.assert(service && service.workspaces.length === 1, "atomReadDocument deserialized a service document");
+        djstest.assert(feed && feed.results.length === 0, "atomReadDocument deserialized a feed document");
+        djstest.assert(entry && !entry.results && entry.__metadata.uri === "http://services.odata.org/OData/OData.svc/entry id", "atomReadDocument deserialized a entry document");
+        djstest.assertAreEqual(nonAtom, undefined, "atomReadDocument returns undefined with non Atom input");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadFeedWithActionsAndFunctionsTest() {
+        var feed = "\r\n\
+        <feed xml:base='http://services.odata.org/OData/OData.svc/' \r\n\
+              xmlns:app='http://www.w3.org/2007/app' \r\n\
+              xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n\
+              xmlns:me='http://myExtensions' \r\n\
+              xmlns='http://www.w3.org/2005/Atom'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <m:action metadata='#EntityContainer.Action1' title='Action1' target='http://service/entities(0)/action' /> \r\n\
+           <m:action metadata='#EntityContainer.Action2' title='Action2' target='entities(0)/action2'/> \r\n\
+           <m:action metadata='http://someService/$metadata#Container.Action1' title='Action1' target='http://someService/action' /> \r\n\
+           <m:function metadata='#EntityContainer.Function1' title='Function1' target='http://service/entities(0)/function' /> \r\n\
+           <m:function metadata='#EntityContainer.Function2' title='Function2' target='entities(0)/function2' /> \r\n\
+           <m:function metadata='http://someService/$metadata#Container.Function1' title='Function1' target='http://someService/function' /> \r\n\
+        </feed> \r\n";
+
+        var expected = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: [],
+                actions: [
+                    {
+                        metadata: "#EntityContainer.Action1",
+                        title: "Action1",
+                        target: "http://service/entities(0)/action",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Action2",
+                        title: "Action2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/action2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Action1",
+                        title: "Action1",
+                        target: "http://someService/action",
+                        extensions: []
+                    }
+                ],
+                functions: [
+                    {
+                        metadata: "#EntityContainer.Function1",
+                        title: "Function1",
+                        target: "http://service/entities(0)/function",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Function2",
+                        title: "Function2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/function2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Function1",
+                        title: "Function1",
+                        target: "http://someService/function",
+                        extensions: []
+                    }
+                ]
+            },
+            results: []
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml", "OData-Version": "4.0" }, body: feed };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, expected, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadFeedExtensionsTest() {
+        var feedWithExtensionsString = "\
+         <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:me=\'http://myExtensions' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' attr1=\'a1\' me:attr2=\'a2\'> \r\n\
+           <me:element1>e1</me:element1> \r\n\
+           <me:element2> \r\n\
+               <me:element21 attr3=\'a3\' me:attr4=\'a4\' >e1</me:element21> \r\n\
+           </me:element2> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+        </feed> \r\n"
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(feedWithExtensionsString).documentElement);
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a payload with feed extensions");
+        djstest.assertAreEqual(feed.__metadata.feed_extensions.length, 4, "atomReadFeed didn't return the expected number of extensions");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadFeedLinksTest() {
+        var feedLinksString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+          <link rel=\'next\' href=\'http://nexturi\' me:attr1=\'a1\' attr2=\'a2\'/> \r\n\
+          <link rel=\'self\' href=\'http://selfuri\' me:attr3=\'a1\' attr4=\'a2\'/> \r\n\
+          <link rel=\'alternate\' href=\'http://otheruri\'/> \r\n\
+        </feed> \r\n";
+
+        var root = datajs.xmlParse(feedLinksString).documentElement;
+        var feed = { __metadata: {} };
+        datajs.xmlChildElements(root, function (child) {
+            OData.atomReadFeedLink(child, feed);
+        });
+
+        djstest.assertAreEqual(feed.__next, "http://nexturi", "atomReadFeedLink didn't read the next link element");
+        djstest.assertAreEqual(feed.__metadata.next_extensions.length, 2, "atomReadFeedLink didn't return the expected number of next link extensions");
+        djstest.assertAreEqual(feed.__metadata.self, "http://selfuri", "atomReadFeedLink didn't read the self link element");
+        djstest.assertAreEqual(feed.__metadata.self_extensions.length, 2, "atomReadFeedLink didn't return the expected number of self link extensions");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadLinkTest() {
+        var linkString = "\
+        <link xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' \r\n\
+              rel=\'next\' \r\n\
+              href=\'http://nexturi\' \r\n\
+              type=\'application/atom+xml;type=feed\' \r\n\
+              me:attr1=\'a1\' \r\n\
+              attr2=\'a2\'/> \r\n";
+
+        var link = OData.atomReadLink(datajs.xmlParse(linkString).documentElement);
+
+        djstest.assert(link, "atomReadLink didn't return a link object");
+        djstest.assertAreEqual(link.href, "http://nexturi", "atomReadLink, link object href field has an unexpected value");
+        djstest.assertAreEqual(link.rel, "next", "atomReadLink, link object rel field has an unexpected value");
+        djstest.assertAreEqual(link.type, "application/atom+xml;type=feed", "atomReadLink, link object type field has an unexpected value");
+        djstest.assertAreEqual(link.extensions.length, 2, "atomReadLink, link object extensions doesn't have the expected number of extensions");
+
+        djstest.done();
+
+    });
+
+    djstest.addFullTest(true, function atomReadLinkThrowHrefMissingTest() {
+        var linkString = "\
+        <link xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' \r\n\
+              rel=\'next\' \r\n\
+              type=\'application/atom+xml;type=feed\' \r\n\
+              me:attr1=\'a1\' \r\n\
+              attr2=\'a2\'/> \r\n";
+
+
+        var linkRoot = datajs.xmlParse(linkString).documentElement;
+        djstest.expectException(function () {
+            OData.atomReadLink(linkRoot);
+        }, "atomReadLink didn't throw an exception when the link doesn't have the href attribute");
+        djstest.done();
+
+    });
+
+    djstest.addFullTest(true, function atomReadExtensionElementTest() {
+        var extensionString = "\
+        <me:ext xmlns:me=\'http://myExtensions\' me:attr1=\'a1\' attr2=\'a2\'> \r\n\
+          <ext>e1</ext> \r\n\
+        </me:ext> \r\n";
+
+        var validateExtension = function (ext, name, namespaceURI, attributeCount, childrenCount, value) {
+            djstest.assertAreEqual(ext.name, name, "atomReadExtensionElement, extension object name field has an unexpected value");
+            djstest.assertAreEqual(ext.namespaceURI, namespaceURI, "atomReadExtensionElement, extension object namespaceURI field has an unexpected value");
+            djstest.assertAreEqual(ext.attributes.length, attributeCount, "atomReadExtensionElement, extension object attributes doesn't have the expected number of attributes");
+            djstest.assertAreEqual(ext.children.length, childrenCount, "atomReadExtensionElement, extension object attributes doesn't have the expected number of children");
+            djstest.assertAreEqual(ext.value, value, "atomReadExtensionElement, extension object value field has an unexpected value");
+        };
+
+        var extension = OData.atomReadExtensionElement(datajs.xmlParse(extensionString).documentElement);
+        validateExtension(extension, "ext", "http://myExtensions", 2, 1);
+
+        extension = extension.children[0];
+        validateExtension(extension, "ext", null, 0, 0, "e1");
+
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadExtensionAttributesTest() {
+        var extensionString = "\
+        <me:ext xmlns:me=\'http://myExtensions\' me:attr1=\'a1\' attr2=\'a2\' /> \r\n";
+
+        var extensionAttributes = OData.atomReadExtensionAttributes(datajs.xmlParse(extensionString).documentElement);
+        djstest.assertAreEqual(extensionAttributes.length, 2, "atomReadExtensionAttribute, returned collection doesn't have the expected number of attributes");
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadExtensionAttributeTest() {
+
+        var tests = {
+            "extension with namespace": {
+                i: '<me:ext xmlns:me="http://myExtensions" me:attr1="a1" />',
+                e: { name: "attr1", namespaceURI: "http://myExtensions", value: "a1" }
+            },
+            "extension without namespace": {
+                i: '<me:ext xmlns:me="http://myExtensions" attr2="a2" />',
+                e: { name: "attr2", namespaceURI: null, value: "a2" }
+            }
+        };
+
+        for (var name in tests) {
+            var test = tests[name];
+            var xmlElement = datajs.xmlParse(test.i).documentElement;
+            var extensions = OData.atomReadExtensionAttributes(xmlElement);
+
+            djstest.assertAreEqualDeep(extensions[0], test.e, name + " - extension object is the expected one");
+        }
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadEntryTest() {
+        var entryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:d2=\'http://schemas.microsoft.com/ado/2007/08/dataservices\' \r\n\
+               xmlns:m2=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>the id</id> \r\n\
+           <category term=\'the type\' \r\n\
+                     scheme=\'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\' /> \r\n\
+           <content type=\'application/xml\'> \r\n\
+            <m2:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n\
+             <Untyped>untyped value</Untyped> \r\n\
+             <Typed m2:type='Edm.Int32'>100</Typed> \r\n\
+            </m2:properties> \r\n\
+           </content> \r\n\
+           <link rel=\'self\' href=\'http://selfuri\' /> \r\n\
+        </entry>\r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                self: "http://selfuri",
+                self_link_extensions: [],
+                properties: {
+                    Untyped: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    }
+                }
+            },
+            Untyped: "untyped value",
+            Typed: 100
+        };
+
+        var entry = OData.atomReadEntry(datajs.xmlParse(entryString).documentElement);
+
+        djstest.assert(entry, "atomReadEntry didn't return an entry object");
+        djstest.assertAreEqualDeep(entry, expectedEntry);
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadEntryGmlCRSValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <PointQualified>                                                            \r\n\
+                   <gml:Point gml:srsName='http://www.opengis.net/def/crs/EPSG/0/1234'>      \r\n\
+                      <gml:pos>1 2 3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointQualified>                                                           \r\n\
+                 <PointUnQualified>                                                          \r\n\
+                   <gml:Point srsName='http://www.opengis.net/def/crs/EPSG/0/5678'>          \r\n\
+                      <gml:pos>5 6 7 8</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointUnQualified>                                                         \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    PointQualified: { type: "Edm.Geometry", extensions: [] },
+                    PointUnQualified: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            PointQualified: {
+                __metadata: { type: "Edm.Geometry" },
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:1234"
+                    }
+                },
+                type: "Point",
+                coordinates: [1, 2, 3, 4]
+            },
+            PointUnQualified: {
+                __metadata: { type: "Edm.Geometry" },
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:5678"
+                    }
+                },
+                type: "Point",
+                coordinates: [5, 6, 7, 8]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadEntryGmlUnknownCRSValueThrowsTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <Point>                                                                     \r\n\
+                   <gml:Point srsName='http://www.opengis.net/def/crs/EPSG/1/1234'>          \r\n\
+                      <gml:pos>1 2 3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                     \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        try {
+            OData.atomHandler.read(response);
+            djstest.fail("An exception was expected");
+        } catch (e) {
+            djstest.assert(e.message.indexOf("Unsupported srs name:") === 0, "Error is the expected one");
+        }
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadEntryGmlPointValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <Point>                                                                     \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>1 2 -3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                    \r\n\
+                 <PointWithExtraTags>                                                        \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:name>the point</gml:name>                                         \r\n\
+                      <gml:pos>5 6 7 8</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithExtraTags>                                                       \r\n\
+                 <EmptyPoint >                                                               \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos/>                                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </EmptyPoint>                                                               \r\n\
+                 <PointWithSpacesInValue>                                                    \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>  8  9 10   11      12 </gml:pos>                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSpacesInValue>                                                   \r\n\
+                 <PointWithSingleValue>                                                      \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>13</gml:pos>                                                  \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSingleValue>                                                     \r\n\
+                 <PointWithSingleValueAndSpaces>                                             \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos> 14 </gml:pos>                                                \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSingleValueAndSpaces>                                            \r\n\
+              </m:properties>                                                                \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    Point: { type: "Edm.Geometry", extensions: [] },
+                    PointWithExtraTags: { type: "Edm.Geometry", extensions: [] },
+                    EmptyPoint: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSpacesInValue: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSingleValue: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSingleValueAndSpaces: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            Point: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [1, 2, -3, 4]
+            },
+            PointWithExtraTags: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [5, 6, 7, 8]
+            },
+            EmptyPoint: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: []
+            },
+            PointWithSpacesInValue: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [8, 9, 10, 11, 12]
+            },
+            PointWithSingleValue: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [13]
+            },
+            PointWithSingleValueAndSpaces: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [14]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addFullTest(true, function atomReadEntryGmlLineStringValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+                <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'  \r\n\
+                             xmlns:gml='http://www.opengis.net/gml'>                         \r\n\
+                 <LineStringExtraTags>                                                       \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:name>the line</gml:name>                                           \r\n\
+                     <gml:posList>1.0 2.0 3.0 4.0</gml:posList>                              \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringExtraTags>                                                      \r\n\
+                 <LineStringPosList>                                                         \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList>5.0 6.0 7.0 8.0</gml:posList>                              \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringPosList>                                                        \r\n\
+                 <LineStringEmptyPosList>                                                    \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList/>                                                          \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringEmptyPosList>                                                   \r\n\
+                 <LineStringPosAndPoint>                                                     \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:pos>7 8</gml:pos>                                                  \r\n\
+                     <gml:pointProperty>                                                     \r\n\
+                        <gml:Point>                                                          \r\n\
+                          <gml:pos>9 10 11 12</gml:pos>                                      \r\n\
+                        </gml:Point>                                                         \r\n\
+                     </gml:pointProperty>                                                    \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringPosAndPoint>                                                    \r\n\
+                 <LineStringEmptyPosAndPoint>                                                \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:pos/>                                                              \r\n\
+                     <gml:pointProperty>                                                     \r\n\
+                        <gml:Point>                                                          \r\n\
+                          <gml:pos/>                                                         \r\n\
+                        </gml:Point>                                                         \r\n\
+                     </gml:pointProperty>                                                    \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringEmptyPosAndPoint>                                               \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    LineStringExtraTags: { type: "Edm.Geometry", extensions: [] },
+                    LineStringPosList: { type: "Edm.Geometry", extensions: [] },
+                    LineStringEmptyPosList: { type: "Edm.Geometry", extensions: [] },
+                    LineStringPosAndPoint: { type: "Edm.Geometry", extensions: [] },
+                    LineStringEmptyPosAndPoint: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            LineStringExtraTags: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "LineString",
+                coordinates: [[1, 2], [3, 4]]
+            },
+            LineStringPosList: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "LineString",
+                coordinate

<TRUNCATED>

[09/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/endpoints/CustomAnnotations.xml
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/CustomAnnotations.xml b/datajs/tests/endpoints/CustomAnnotations.xml
new file mode 100644
index 0000000..622769f
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/endpoints/CustomDataService.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/CustomDataService.svc b/datajs/tests/endpoints/CustomDataService.svc
new file mode 100644
index 0000000..f30a261
--- /dev/null
+++ b/datajs/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

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/endpoints/EpmDataService.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/EpmDataService.svc b/datajs/tests/endpoints/EpmDataService.svc
new file mode 100644
index 0000000..ba90c23
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/endpoints/ErrorDataService.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/ErrorDataService.svc b/datajs/tests/endpoints/ErrorDataService.svc
new file mode 100644
index 0000000..d25e30e
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/endpoints/FoodStoreDataServiceV4.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/FoodStoreDataServiceV4.svc b/datajs/tests/endpoints/FoodStoreDataServiceV4.svc
new file mode 100644
index 0000000..21f09cf
--- /dev/null
+++ b/datajs/tests/endpoints/FoodStoreDataServiceV4.svc
@@ -0,0 +1,589 @@
+<%@ ServiceHost Language="C#" Factory="Microsoft.OData.Service.DataServiceHostFactory, Microsoft.OData.Service, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.V4.FoodStoreDataService" %>
+
+// 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.V4
+{
+    using System;
+    using System.Collections.Generic;
+    using Microsoft.OData.Service;
+    using Microsoft.OData.Service.Common;
+    using Microsoft.OData.Service.Providers;
+    using System.Linq;
+    using System.ServiceModel.Web;
+    using System.Web;
+    using System.IO;
+    using Microsoft.Spatial;
+    
+    /// <summary>
+    /// Provides a service similar to FoodStoreDataService, but uses V4 and WCF Data Services 6.0.0-beta1
+    /// features.
+    /// </summary>
+    [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    public class FoodStoreDataService : DataService<FoodContainer>
+    {
+        // 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;
+            // Set Foods page size to 5 for cache testing
+            config.SetEntitySetPageSize("Foods", 5);
+            // Make the Categories set paged to have a paged feed
+            config.SetEntitySetPageSize("Categories", 1);
+        }
+        
+        [WebInvoke]
+        public string ResetData()
+        {
+            this.CurrentDataSource.ResetData();
+            return "Data Reset";
+        }
+
+        [WebGet]
+        public IQueryable<string> FoodsAvailable()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Name);
+        }
+
+        [WebGet]
+        public IQueryable<Package> PackagingTypes()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Packaging);
+        }
+
+        [WebGet]
+        public string UserNameAndPassword()
+        {
+            var request = WebOperationContext.Current.IncomingRequest;
+            string authorization = request.Headers["Authorization"];
+            if (String.IsNullOrEmpty(authorization))
+            {
+                WebOperationContext.Current.OutgoingResponse.Headers["WWW-Authenticate"] = "Basic realm=\"localhost\"";
+                throw new DataServiceException(401, "Access denied in UserNameAndPassword");
+            }
+
+            return authorization;
+        }
+    }
+
+    public class FoodContainer : ReflectionDataContext, IUpdatable, IDataServiceStreamProvider2
+    {
+        private static bool dataInitialized;
+
+        public IQueryable<Category> Categories
+        {
+            get { return this.GetResourceSetEntities<Category>("Categories").AsQueryable(); }
+        }
+        
+        public IQueryable<Food> Foods
+        {
+            get { return this.GetResourceSetEntities<Food>("Foods").AsQueryable(); }
+        }
+
+        public void ResetData()
+        {
+            this.ClearData();
+
+            var builder = SpatialImplementation.CurrentImplementation.CreateBuilder();
+            builder.GeometryPipeline.SetCoordinateSystem(CoordinateSystem.DefaultGeography);
+            builder.GeometryPipeline.BeginGeometry(SpatialType.Collection);
+            builder.GeometryPipeline.BeginFigure(new GeometryPosition(5.0, 5.0));
+            builder.GeometryPipeline.EndFigure();
+            builder.GeometryPipeline.EndGeometry();
+            
+            int i = 0;
+            Category[] categories = new Category[]
+            {
+                new Category { CategoryID = i++, Name = "Baking Supplies" },
+                new Category { CategoryID = i++, Name = "Condiments" },
+                new Category { CategoryID = i++, Name = "Empty Category" }
+            };
+            Array.ForEach(categories, (category) => this.GetResourceSetEntities<Category>("Categories").Add(category));
+            
+            i = 0;
+            Food[] foods = new Food[]
+            {            
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "flour",
+                    UnitPrice = .19999,
+                    ServingSize = 1,
+                    MeasurementUnit = "Cup",
+                    ProteinGrams = 3,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 20,
+                    CaloriesPerServing = 140,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2010, 12, 25, 12, 0, 0),
+                    ItemGUID = new Guid("27272727272727272727272727272727"),
+                    Weight = 10f,
+                    AvailableUnits = 1,
+                    
+                    Packaging = new Package(){
+                        Type = null, 
+                        Color = String.Empty, 
+                        NumberPerPackage = int.MaxValue, 
+                        RequiresRefridgeration = false, 
+                        PackageDimensions = new Dimensions()
+                        {
+                            Length = Decimal.MaxValue, 
+                            Height = Int16.MaxValue, 
+                            Width = Int64.MaxValue, 
+                            Volume = double.MaxValue,   
+                        },
+                        ShipDate = new DateTime(2000, 12, 29)
+                    },
+                    
+                    CookedSize = new CookedDimensions()
+                    {
+                        Height = 1,
+                        Length = 2,
+                        Width = 3,
+                        Volume = 1 * 2 * 3
+                    },
+                    
+                    Category = categories[0],
+                    
+                    AlternativeNames = new List<string>() {"ground cereal", "ground grain"},
+                    
+                    Providers = new List<Provider> {
+                        new Provider() { 
+                             Name= "Flour Provider", 
+                             Aliases = new List<string>() {"fp1", "flour provider1"},
+                             Details = new ProviderDetails() {
+                                 Telephone= "555-555-555",
+                                 PreferredCode = 1001
+                             }
+                        },
+                        new Provider() { 
+                             Name= "Ground Grains", 
+                             Aliases = new List<string>()
+                        }
+                    },
+                    
+                    SpatialData = (GeometryCollection)builder.ConstructedGeometry 
+                },
+                
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "sugar",
+                    UnitPrice = .2,
+                    ServingSize = 1,
+                    MeasurementUnit = "tsp",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 4,
+                    CaloriesPerServing = 16,
+                    IsAvailable = false,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+                    Weight = 0.1f,
+                    AvailableUnits = 0,
+
+                    Packaging = new Package(){
+                        Type = " ",
+                        Color = "BLUE",
+                        NumberPerPackage = int.MinValue,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = Decimal.MinValue,
+                            Height = Int16.MinValue,
+                            Width = Int64.MinValue,
+                            Volume = double.MinValue,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = categories[1],
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "1 Chicken Egg",
+                    UnitPrice = 0.55,
+                    MeasurementUnit = null,
+                    ServingSize = 1,
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 1,
+                    CaloriesPerServing = 70,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("00000000000000000000000000000000"),
+                    Weight = 0,
+                    AvailableUnits = -128,
+                    
+                    Packaging = new Package(){
+                        Type = "18     - Carton",
+                        Color = " brown ",
+                        NumberPerPackage = 0,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = null,
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = null,
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "Brown Sugar",
+                    UnitPrice = 1.6,
+                    ServingSize = 1,
+                    MeasurementUnit = "TSP.",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 5, 
+                    CaloriesPerServing = 16,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 4.5f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Cobb Salad",
+                    UnitPrice = 1.99,
+                    ServingSize = -1,
+                    MeasurementUnit = "cups",
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 3, 
+                    CaloriesPerServing = 5,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 5.674f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                    Instructions = "1.) Open 2.) Eat",
+                    NumberOfIngredients = 4,
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Lasagna",
+                    UnitPrice = 0,
+                    ServingSize = 8,
+                    MeasurementUnit = " servings",
+                    ProteinGrams = 100,
+                    FatGrams = 4,
+                    CarbohydrateGrams = 27, 
+                    CaloriesPerServing = 389,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(1904, 2, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 0,
+                    AvailableUnits = 4,
+                    Packaging = new Package(){
+                        Type = "box",
+                        Color = " 1 ",
+                        NumberPerPackage = 1,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = 3,
+                            Height = 1,
+                            Width = 5,
+                            Volume = 1.5,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    Category = categories[0],
+                    Instructions = "Bake in oven",
+                    NumberOfIngredients = 15,
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Chocolate"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Pizza"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Avocados"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Quinoa"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Oatmeal"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Peanut Butter"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Banana"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Yam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Clam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Spam"
+                }
+            };
+            Array.ForEach(foods, (food) => this.GetResourceSetEntities<Food>("Foods").Add(food));
+
+            categories[0].Foods.Add(foods[0]);
+            categories[1].Foods.Add(foods[2]);
+            categories[1].Foods.Add(foods[3]);
+        }
+
+        protected override void EnsureDataIsInitialized()
+        {
+            if (!dataInitialized)
+            {
+                this.ResetData();
+                dataInitialized = true;
+            }
+        }
+
+        public Stream GetReadStream(object entity, ResourceProperty streamProperty, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            return new MemoryStream();
+        }
+
+        public Uri GetReadStreamUri(object entity, ResourceProperty streamProperty, DataServiceOperationContext operationContext)
+        {
+            if (streamProperty.Name == "Icon")
+            {
+                return null;
+            }
+            return new Uri(operationContext.AbsoluteServiceUri, streamProperty.Name);
+        }
+
+        public string GetStreamContentType(object entity, ResourceProperty streamProperty, DataServiceOperationContext operationContext)
+        {
+            if (streamProperty.Name == "Icon")
+            {
+                return "image/gif";
+            }
+            return "image/png";
+        }
+
+        public string GetStreamETag(object entity, ResourceProperty streamProperty, DataServiceOperationContext operationContext)
+        {
+            return "W/\"123456789\"";
+        }
+
+        public Stream GetWriteStream(object entity, ResourceProperty streamProperty, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            return new MemoryStream();
+        }
+
+        public void DeleteStream(object entity, DataServiceOperationContext operationContext)
+        {
+            // do nothing.
+        }
+
+        public Stream GetReadStream(object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Uri GetReadStreamUri(object entity, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public string GetStreamContentType(object entity, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public string GetStreamETag(object entity, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Stream GetWriteStream(object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public string ResolveType(string entitySetName, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public int StreamBufferSize
+        {
+            get { return 1024; }
+        }
+    }
+
+    /// <summary>
+    /// The Category class is a simple class with V1-compatible feed customizations.
+    /// </summary>
+    [DataServiceKey("CategoryID")]
+    [EntitySet("Categories")]
+    [NamedStream("Icon")]
+    public class Category
+    {
+        public Category()
+        {
+            this.Foods = new List<Food>();
+        }
+        
+        public int CategoryID { get; set; }
+        public string Name { get; set; }
+        public List<Food> Foods { get; set; }
+    }
+    
+    /// <summary>
+    /// The Food class has a mixture of V1-compatible and incompatible feed
+    /// customizations (thus it's V2), and custom mappings.
+    /// </summary>
+    [DataServiceKey("FoodID")]
+    [EntitySet("Foods")]
+    [NamedStream("Picture")]
+    public class Food
+    {
+        private List<string> alternativeNames = new List<string>();
+        private List<Provider> providers = new List<Provider>();
+        
+        // Primitive types
+        public int FoodID { get; set; }
+        public string Name { get; set; }
+        public double UnitPrice { get; set; }
+        public Decimal ServingSize { get; set; }
+        public string MeasurementUnit { get; set; }
+        public Byte ProteinGrams { get; set; }
+        public Int16 FatGrams { get; set; }
+        public Int32 CarbohydrateGrams { get; set; }
+        public Int64 CaloriesPerServing { get; set; }
+        public Boolean IsAvailable { get; set; }
+        public DateTime ExpirationDate { get; set; }
+        public Guid ItemGUID { get; set; }
+        public Single Weight { get; set; }
+        public sbyte AvailableUnits { get; set; }
+
+        // Complex types
+        public Package Packaging { get; set; }
+        public CookedDimensions CookedSize { get; set; }
+
+        // Navigation properties
+        public Category Category { get; set; }
+
+        // Collection properties
+        public List<string> AlternativeNames
+        {
+            get { return alternativeNames; }
+            set { alternativeNames = value; }
+        }
+
+        public List<Provider> Providers
+        {
+            get { return providers; }
+            set { providers = value; }
+        }
+
+        public GeometryCollection SpatialData
+        {
+            get;
+            set;
+        }
+        
+    }
+
+    public class Provider
+    {
+        public string Name { get; set; }
+        public List<string> Aliases { get; set; }
+        public ProviderDetails Details { get; set; }
+    }
+
+    public class ProviderDetails
+    {
+        public string Telephone { get; set; }
+        public int PreferredCode { get; set; }
+    }
+    
+    public class Package
+    {
+        public string Type { get; set; }
+        public string Color { get; set; }
+        public int NumberPerPackage { get; set; }
+        public Boolean RequiresRefridgeration { get; set; }
+        public DateTime ShipDate { get; set; }
+        public Dimensions PackageDimensions { get; set; }
+    }
+
+    public class Dimensions
+    {
+        public Decimal Length { get; set; }
+        public Int16 Height { get; set; }
+        public Int64 Width { get; set; }
+        public double Volume { get; set; }
+    }
+
+    public class CookedDimensions
+    {
+        public Decimal Length { get; set; }
+        public Int16 Height { get; set; }
+        public Int64 Width { get; set; }
+        public double Volume { get; set; }
+    }
+
+    public class PreparedFood : Food
+    {
+        public string Instructions { get; set; }
+        public float NumberOfIngredients { get; set; }
+    }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/endpoints/LargeCollectionService.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/LargeCollectionService.svc b/datajs/tests/endpoints/LargeCollectionService.svc
new file mode 100644
index 0000000..122d045
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/endpoints/web.config
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/web.config b/datajs/tests/endpoints/web.config
new file mode 100644
index 0000000..5836287
--- /dev/null
+++ b/datajs/tests/endpoints/web.config
@@ -0,0 +1,26 @@
+<?xml version='1.0'?>
+<configuration>
+  <system.web>
+    <compilation debug='true'>
+      <assemblies>
+        <add assembly='System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35'/>
+        <add assembly='System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35'/>
+        <add assembly="Microsoft.OData.Core, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+        <add assembly="Microsoft.OData.Service, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+        <add assembly="Microsoft.OData.Client, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+      </assemblies>
+    </compilation>
+  </system.web>
+  <system.codedom>
+    <compilers>
+      <compiler language='c#;cs;csharp' extension='.cs' type='Microsoft.CSharp.CSharpCodeProvider,System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'>
+        <providerOption name='CompilerVersion' value='v4.0' />
+      </compiler>
+    </compilers>
+  </system.codedom>
+</configuration>


[07/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-batch-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-batch-functional-tests.html b/datajs/tests/odata-batch-functional-tests.html
new file mode 100644
index 0000000..c2c3759
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-batch-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-batch-functional-tests.js b/datajs/tests/odata-batch-functional-tests.js
new file mode 100644
index 0000000..b30e52c
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-batch-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-batch-tests.js b/datajs/tests/odata-batch-tests.js
new file mode 100644
index 0000000..9aeb033
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-cache-filter-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-filter-functional-tests.html b/datajs/tests/odata-cache-filter-functional-tests.html
new file mode 100644
index 0000000..9c5da7e
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-cache-filter-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-filter-functional-tests.js b/datajs/tests/odata-cache-filter-functional-tests.js
new file mode 100644
index 0000000..b11cad3
--- /dev/null
+++ b/datajs/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);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-cache-fperf-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-fperf-tests.html b/datajs/tests/odata-cache-fperf-tests.html
new file mode 100644
index 0000000..3323199
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-cache-fperf-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-fperf-tests.js b/datajs/tests/odata-cache-fperf-tests.js
new file mode 100644
index 0000000..441022c
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-cache-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-cache-functional-tests.html b/datajs/tests/odata-cache-functional-tests.html
new file mode 100644
index 0000000..e6f2992
--- /dev/null
+++ b/datajs/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>


[03/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-metadata-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-metadata-tests.js b/datajs/tests/odata-metadata-tests.js
new file mode 100644
index 0000000..85e96ed
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-net-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-net-tests.js b/datajs/tests/odata-net-tests.js
new file mode 100644
index 0000000..35cd6c2
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-perf-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-perf-tests.html b/datajs/tests/odata-perf-tests.html
new file mode 100644
index 0000000..bdcbfb5
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-perf-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-perf-tests.js b/datajs/tests/odata-perf-tests.js
new file mode 100644
index 0000000..f57bcc0
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-qunit-tests.htm
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-qunit-tests.htm b/datajs/tests/odata-qunit-tests.htm
new file mode 100644
index 0000000..b2bce5a
--- /dev/null
+++ b/datajs/tests/odata-qunit-tests.htm
@@ -0,0 +1,98 @@
+<!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" />
+    <meta http-equiv="cache-control" content="no-cache" />
+    <meta http-equiv="pragma" content="no-cache" />
+    <meta http-equiv="expires" content="-1" />
+
+    <title>OData unit tests</title>
+    <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.10.0.css" type="text/css" />
+    
+    <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/jquery-1.4.4.js">
+    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.2.js"></script>-->
+    
+
+    <script type="text/javascript" src="http://code.jquery.com/qunit/qunit-1.10.0.js"></script>
+    <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
+
+    <script type="text/javascript" src="common/ODataReadOracle.js"></script>
+    <script type="text/javascript" src="common/TestSynchronizerClient.js"></script>
+
+    <script type="text/javascript" src="common/rx.js"></script>
+
+    <script type="text/javascript">
+        window.TestSynchronizer.init(QUnit);
+    </script>
+   
+<script type="text/javascript" src="../build/datajs-0.0.0.js"></script>   
+<!--SK TODO enable    <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/mockHttpClient.js"></script>
+<!--SK TODO enable        <script type="text/javascript" src="common/mockXMLHttpRequest.js"></script>-->
+
+    <script type="text/javascript" src="./common/djstest.js"></script>
+<!--SK TODO enable        <script type="text/javascript" src="common/CacheOracle.js"></script>-->
+
+<!--bingl: disable the failure test case. Will fix them in the next change set-->
+<!--    <script type="text/javascript" src="odata-tests.js"></script>-->
+<!--    <script type="text/javascript" src="odata-atom-tests.js"></script>-->
+    <script type="text/javascript" src="odata-json-tests.js"></script>
+<!--    <script type="text/javascript" src="odata-json-light-tests.js"></script>-->
+<!--SK TODO enable    <script type="text/javascript" src="odata-metadata-tests.js"></script>-->
+<!--SK TODO enable    <script type="text/javascript" src="odata-xml-tests.js"></script>-->
+<!--SK TODO enable    <script type="text/javascript" src="odata-handler-tests.js"></script>-->
+<!--SK TODO enable    <script type="text/javascript" src="odata-net-tests.js"></script>-->
+<!--SK TODO enable    <script type="text/javascript" src="odata-batch-tests.js"></script>-->
+
+<!--SK TODO enable    <script type="text/javascript" src="cache-tests.js"></script>-->
+<!--SK TODO enable    <script type="text/javascript" src="store-tests.js"></script>-->
+<!--SK TODO enable    <script type="text/javascript" src="store-indexeddb-tests.js"></script>-->
+  </head>
+  <body>
+    <h1 id="qunit-header">OData Unit 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/0367d2bc/datajs/tests/odata-read-crossdomain-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-read-crossdomain-functional-tests.html b/datajs/tests/odata-read-crossdomain-functional-tests.html
new file mode 100644
index 0000000..f3e18a6
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-read-crossdomain-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-read-crossdomain-functional-tests.js b/datajs/tests/odata-read-crossdomain-functional-tests.js
new file mode 100644
index 0000000..7d1a013
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-read-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-read-functional-tests.html b/datajs/tests/odata-read-functional-tests.html
new file mode 100644
index 0000000..0a60a3a
--- /dev/null
+++ b/datajs/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>


[10/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/common/djstest.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/djstest.js b/datajs/tests/common/djstest.js
new file mode 100644
index 0000000..b9df624
--- /dev/null
+++ b/datajs/tests/common/djstest.js
@@ -0,0 +1,409 @@
+// 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.
+
+(function (window, undefined) {
+    var djstest = {};
+
+    window.djstest = djstest;
+
+    djstest.indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB;
+
+    djstest.cleanStoreOnIndexedDb = function (storeObjects, done) {
+        /// <summary>Cleans all the test data saved in the IndexedDb database.</summary>
+        /// <param name="storeNames" type="Array">Array of store objects with a property that is the name of the store</param>
+        /// <param name="done" type="Function">Callback function</param>
+
+        var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {};
+
+        var deleteObjectStores = function (db) {
+            $.each(db.objectStoreNames, function (_, storeName) {
+                db.deleteObjectStore(storeName);
+            });
+        };
+
+        if (djstest.indexedDB) {
+            var job = new djstest.Job();
+            $.each(storeObjects, function (_, storeObject) {
+                job.queue((function (storeObject) {
+                    return function (success, fail) {
+                        var dbname = "_datajs_" + storeObject.name;
+                        var request = djstest.indexedDB.open(dbname);
+                        request.onsuccess = function (event) {
+                            var db = request.result;
+
+                            if ("setVersion" in db) {
+                                var versionRequest = db.setVersion("0.1");
+                                versionRequest.onsuccess = function (event) {
+                                    var transaction = versionRequest.transaction;
+                                    transaction.oncomplete = function () {
+                                        db.close();
+                                        success();
+                                    }
+                                    deleteObjectStores(db);
+                                };
+                                versionRequest.onerror = function (e) {
+                                    djstest.fail("Error on cleanup - code: " + e.code + " name: " + e.name + "message: " + message);
+                                    fail();
+                                };
+                                return;
+                            }
+
+                            // new api cleanup
+                            db.close();
+                            var deleteRequest = djstest.indexedDB.deleteDatabase(dbname);
+                            deleteRequest.onsuccess = function (event) {
+                                djstest.log("djstest indexeddb cleanup - deleted database " + dbname);
+                                success();
+                            };
+                            deleteRequest.onerror = function (e) {
+                                djstest.fail("djstest indexeddb cleanup - error deleting database " + dbname);
+                                fail();
+                            };
+                            djstest.log("djstest indexeddb cleanup - requested deletion of database " + dbname);
+                        };
+
+                        request.onerror = function (e) {
+                            djstest.fail(e.code + ": " + e.message);
+                        };
+                    };
+                })(storeObject));
+            });
+        }
+
+        if (job) {
+            job.run(function (succeeded) {
+                if (!succeeded) {
+                    djstest.fail("cleanup job failed");
+                }
+                done();
+            });
+        }
+        else {
+            done();
+        }
+    };
+
+    djstest.Job = function () {
+        /// <summary>Constructs a Job object that allows for enqueuing and synchronizing the execution of functions.</summary>
+        /// <returns type="Object">Job object</returns>
+        var currentTask = -1;
+        var tasks = [];
+
+        var failedTasks = 0;
+
+        this.queue = function (fn) {
+            /// <summary>Adds a function to the job queue regardless if the queue is already executing or not.</summary>
+            /// <param name="fn" type="Function">Function to execute.</param>
+            tasks.push(fn);
+        };
+
+        this.queueNext = function (fn) {
+            /// <summary>Adds a function to the front of the job queue regardless if the queue is already executing or not.</summary>
+            /// <param name="fn" type="Function">Function to execute.</param>
+            if (currentTask < 0) {
+                tasks.unshift(fn);
+            } else {
+                tasks.splice(currentTask + 1, 0, fn);
+            }
+        };
+
+        this.run = function (done) {
+            /// <summary>Starts the execution of this job.</summary>
+            /// <param name="done" type="Function">Callback invoked when the job has finished executing all of its enqueued tasks.</param>
+            /// <remarks>
+            /// This method does nothing if called on a unit of work that is already executing.
+            /// </remarks>
+
+            if (currentTask >= 0) {
+                return;
+            }
+
+            if (tasks.length === 0) {
+                done(true);
+                return;
+            }
+
+            var makeTaskDoneCallBack = function (failed) {
+                return function () {
+                    // Track the failed task and continue the execution of the job. 
+                    if (failed) {
+                        failedTasks++;
+                    }
+                    currentTask++;
+                    if (currentTask === tasks.length) {
+                        done(failedTasks === 0);
+                    } else {
+                        runNextTask();
+                    }
+                };
+            };
+
+            var runNextTask = function () {
+                /// <summary>Executes the next function in the queue.</summary>
+                defer(function () {
+                    try {
+                        tasks[currentTask](makeTaskDoneCallBack(false), makeTaskDoneCallBack(true));
+                    } catch (e) {
+                        makeTaskDoneCallBack(true)();
+                    }
+                });
+            };
+
+            currentTask = 0;
+            runNextTask();
+        };
+    };
+
+    var defer = function (fn) {
+        /// <summary>Defers the execution of an arbitrary function that takes no parameters.</summary>
+        /// <param name="fn" type="Function">Function to schedule for later execution.</param>
+        setTimeout(fn, 0);
+    }
+
+    var exposeDateValues = function (data) {
+        /// <summary>Exposes date values for Date objects to facilitate debugging</summary>
+        /// <param name="data" type="Object">The object to operate on</param>
+        if (typeof data === "object") {
+            if (data instanceof Date) {
+                data["__date__"] = data.toUTCString();
+            }
+            else {
+                for (var prop in data) {
+                    exposeDateValues(data[prop]);
+                }
+            }
+        }
+
+        return data;
+    }
+
+    var extractFunctionName = function (text) {
+        /// <summary>Determines the name of a function.</summary>
+        /// <param name="text" type="String">Function text.</param>
+        /// <returns type="String">The name of the function from text if found; the original text otherwise.</returns>
+
+        var index = text.indexOf("function ");
+        if (index < 0) {
+            return text;
+        }
+
+        var nameStart = index + "function ".length;
+        var parensIndex = text.indexOf("(", nameStart);
+        if (parensIndex < 0) {
+            return text;
+        }
+
+        var result = text.substr(nameStart, parensIndex - nameStart);
+        if (result.indexOf("test") === 0) {
+            result = result.substr("test".length);
+        }
+
+        return result;
+    };
+
+    var removeMetadata = function (data) {
+        /// <summary>Removes metadata annotations from the specified object.</summary>
+        /// <param name="data">Object to remove metadata from; possibly null.</param>
+
+        if (typeof data === "object" && data !== null) {
+            delete data["__metadata"];
+            for (prop in data) {
+                removeMetadata(data[prop]);
+            }
+        }
+    };
+
+    djstest.addFullTest = function (disable, fn, name, arg, timeout) {
+        /// <summary>Add the unit test cases</summary>
+        /// <param name="disable">Indicate whether this test case should be disabled</param>
+        if (disable != true) {
+            djstest.addTest(fn, name, arg, timeout);
+        }
+    };
+
+
+    djstest.addTest = function (fn, name, arg, timeout) {
+        if (!name) {
+            name = extractFunctionName(fn.toString());
+        }
+
+        test(name, function () {
+            if (!timeout) {
+                timeout = 20000;
+            }
+
+            QUnit.config.testTimeout = timeout;
+            QUnit.stop();
+            fn.call(this, arg);
+        });
+    };
+
+    djstest.assert = function (test, message) {
+        /// <summary>Asserts that a condition is true.</summary>
+        /// <param name="test" type="Boolean">Condition to test.</param>
+        /// <param name="message" type="String">Text message for condition being tested.</param>
+        QUnit.ok(test, message);
+    };
+
+    djstest.assertAreEqual = function (actual, expected, message) {
+        /// <summary>Asserts that the values of the expected and actualobjects are equal.</summary>
+        QUnit.equal(actual, expected, message);
+    };
+
+    djstest.assertAreEqualDeep = function (actual, expected, message) {
+        /// <summary>Asserts that the actual and expected objects are the same.</summary>
+        QUnit.deepEqual(exposeDateValues(actual), exposeDateValues(expected), message);
+    };
+
+    djstest.assertWithoutMetadata = function (actual, expected, message) {
+        removeMetadata(actual)
+        removeMetadata(expected);
+        djstest.assertAreEqualDeep(actual, expected, message);
+    };
+
+    djstest.asyncDo = function (asyncActions, done) {
+        /// <summary>Calls each async action in asyncActions, passing each action a function which keeps a count and
+        /// calls the passed done function when all async actions complete.</summary>
+        /// <param name="asyncActions" type="Array">Array of asynchronous actions to be executed, 
+        /// each taking a single parameter - the callback function to call when the action is done.</param>
+        /// <param name="done" type="Function">Function to be executed in the last async action to complete.</param>
+        var count = 0;
+        var doneOne = function () {
+            count++;
+            if (count >= asyncActions.length) {
+                done();
+            }
+        };
+
+        if (asyncActions.length > 0) {
+            $.each(asyncActions, function (_, asyncAction) {
+                asyncAction(doneOne);
+            });
+        } else {
+            done();
+        }
+    }
+
+    djstest.clone = function (object) {
+        /// <summary>Makes a deep copy of an object.</summary>
+        return $.extend(true, {}, object);
+    };
+
+    djstest.destroyCacheAndDone = function (cache) {
+        /// <summary>Destroys the cache and then completes the test</summary>
+        /// <param name="cache">The cache to destroy</param>
+        cache.clear().then(function () {
+            djstest.done();
+        }, function (err) {
+            djstest.fail("Failed to destroy cache: " + djstest.toString(err));
+            djstest.done();
+        });
+    };
+
+    djstest.done = function () {
+        /// <summary>Indicates that the currently running test has finished.</summary>
+        QUnit.start();
+    };
+
+    djstest.expectException = function (testFunction, message) {
+        /// <summary>Test passes if and only if an exception is thrown.</summary>
+        try {
+            testFunction();
+            djstest.fail("Expected exception but function succeeded: " + " " + message);
+        }
+        catch (e) {
+            // Swallow exception.
+            djstest.pass("Thrown exception expected");
+        }
+    };
+
+    djstest.assertsExpected = function (asserts) {
+        /// <summary>Indicates the expected number of asserts, fails test if number is not met.</summary>
+        /// <param name="asserts" type="Number">Number of asserts expected in test.</param>
+        expect(asserts);
+    }
+
+    djstest.fail = function (message) {
+        /// <summary>Marks the current test as failed.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        QUnit.ok(false, message);
+    };
+
+    djstest.failAndDoneCallback = function (message, cleanupCallback) {
+        /// <summary>Returns a function that when invoked will fail this test and be done with it.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        /// <param name="cleanupCallback" type="Function" optional="true">Optional cleanup function in case of failure.</param>
+        /// <returns type="Function">A new function.</returns>
+
+        return function (err) {
+            message = "" + message + (err) ? window.JSON.stringify(err) : "";
+            djstest.fail(message);
+            if (cleanupCallback) {
+                try {
+                    cleanupCallback();
+                } catch (e) {
+                    djstest.fail("error during cleanupCallback: " + window.JSON.stringify(e));
+                }
+            }
+
+            djstest.done();
+        };
+    };
+
+    djstest.log = function (message) {
+        /// <summary>Logs a test message.</summary>
+        /// <param name="message" type="String">Test message.</param>
+        var context = { result: true, actual: true, expected: true, message: message };
+        QUnit.log(context);
+    };
+
+    djstest.pass = function (message) {
+        /// <summary>Marks the current test as failed.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        QUnit.ok(true, message);
+    };
+
+    djstest.toString = function (obj) {
+        /// <summary>Dumps the object as a string</summary>
+        /// <param name="obj" type="Object">Object to dump</param>
+        return QUnit.jsDump.parse(obj);
+    };
+
+    djstest.wait = function (fn) {
+        /// <summary>Executes the function, pausing test execution until the callback is called</summary>
+        /// <param name="fn" type="Function">Function to execute; takes one parameter which is the callback</param>
+        /// <remarks>This function is typically used in asynchronous setup/teardown methods</remarks>
+        QUnit.stop();
+        fn(function () {
+            QUnit.start();
+        });
+    };
+
+    // Disable caching to ensure that every test-related AJAX request is actually being sent,
+    // and set up a default error handler
+    $.ajaxSetup({
+        cache: false,
+        error: function (jqXHR, textStatus, errorThrown) {
+            // Work around bug in IE-Mobile on Windows Phone 7
+            if (jqXHR.status !== 1223) {
+                var err = {
+                    status: jqXHR.status,
+                    statusText: jqXHR.statusText,
+                    responseText: jqXHR.responseText
+                };
+                djstest.fail("AJAX request failed with: " + djstest.toString(err));
+            }
+            djstest.done();
+        }
+    });
+})(window);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/common/mockHttpClient.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/mockHttpClient.js b/datajs/tests/common/mockHttpClient.js
new file mode 100644
index 0000000..de2a69c
--- /dev/null
+++ b/datajs/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);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/common/mockXMLHttpRequest.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/mockXMLHttpRequest.js b/datajs/tests/common/mockXMLHttpRequest.js
new file mode 100644
index 0000000..2615e55
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/common/rx.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/rx.js b/datajs/tests/common/rx.js
new file mode 100644
index 0000000..a7f4ea3
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/datajs-cache-large-collection-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/datajs-cache-large-collection-functional-tests.html b/datajs/tests/datajs-cache-large-collection-functional-tests.html
new file mode 100644
index 0000000..bf5cad3
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/datajs-cache-large-collection-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/datajs-cache-large-collection-functional-tests.js b/datajs/tests/datajs-cache-large-collection-functional-tests.js
new file mode 100644
index 0000000..fd4e826
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/datajs-cache-long-haul-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/datajs-cache-long-haul-tests.html b/datajs/tests/datajs-cache-long-haul-tests.html
new file mode 100644
index 0000000..39eda48
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/datajs-startup-perf-test.html
----------------------------------------------------------------------
diff --git a/datajs/tests/datajs-startup-perf-test.html b/datajs/tests/datajs-startup-perf-test.html
new file mode 100644
index 0000000..465ee01
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/endpoints/BasicAuthDataService.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/endpoints/BasicAuthDataService.svc b/datajs/tests/endpoints/BasicAuthDataService.svc
new file mode 100644
index 0000000..66dcb1f
--- /dev/null
+++ b/datajs/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


[13/13] git commit: [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
[OLINGO-238] adopt odata-json-tests.js


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/0367d2bc
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/tree/0367d2bc
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/diff/0367d2bc

Branch: refs/heads/master
Commit: 0367d2bcdf72ac303a28d38a596ff388c2846c8d
Parents: 48761e0
Author: Sven Kobler <sv...@sap.com>
Authored: Fri May 16 13:26:11 2014 +0200
Committer: Sven Kobler <sv...@sap.com>
Committed: Fri May 16 13:26:11 2014 +0200

----------------------------------------------------------------------
 datajs/Gruntfile.js                             |   24 +-
 datajs/src/index.js                             |    2 +-
 datajs/src/lib/odata.js                         |    4 +-
 datajs/tests/cache-tests.js                     | 1191 +++++
 datajs/tests/code/ReflectionDataContext.cs      |  742 +++
 datajs/tests/code/atomreader.cs                 |  796 +++
 datajs/tests/code/csdlreader.cs                 |  186 +
 datajs/tests/code/jsdate.cs                     |   40 +
 datajs/tests/code/jsonobject.cs                 |   80 +
 datajs/tests/code/readerutils.cs                |   68 +
 datajs/tests/common/CacheOracle.js              |  228 +
 datajs/tests/common/Instrument.js               |   48 +
 datajs/tests/common/Instrument.svc              |   71 +
 datajs/tests/common/ODataReadOracle.js          |  205 +
 datajs/tests/common/ODataReadOracle.svc         |  189 +
 datajs/tests/common/ObservableHttpClient.js     |   78 +
 datajs/tests/common/TestLogger.svc              |  846 ++++
 datajs/tests/common/TestSynchronizerClient.js   |  218 +
 datajs/tests/common/djstest.js                  |  409 ++
 datajs/tests/common/mockHttpClient.js           |  107 +
 datajs/tests/common/mockXMLHttpRequest.js       |  192 +
 datajs/tests/common/rx.js                       |    6 +
 ...cache-large-collection-functional-tests.html |   53 +
 ...s-cache-large-collection-functional-tests.js |  170 +
 datajs/tests/datajs-cache-long-haul-tests.html  |  192 +
 datajs/tests/datajs-startup-perf-test.html      |   89 +
 datajs/tests/endpoints/BasicAuthDataService.svc |  108 +
 datajs/tests/endpoints/CustomAnnotations.xml    |  102 +
 datajs/tests/endpoints/CustomDataService.svc    |   76 +
 datajs/tests/endpoints/EpmDataService.svc       |  315 ++
 datajs/tests/endpoints/ErrorDataService.svc     |   56 +
 .../tests/endpoints/FoodStoreDataServiceV4.svc  |  589 +++
 .../tests/endpoints/LargeCollectionService.svc  |   93 +
 datajs/tests/endpoints/web.config               |   26 +
 datajs/tests/odata-atom-tests.js                | 4745 ++++++++++++++++++
 datajs/tests/odata-batch-functional-tests.html  |   43 +
 datajs/tests/odata-batch-functional-tests.js    |  270 +
 datajs/tests/odata-batch-tests.js               |  551 ++
 .../odata-cache-filter-functional-tests.html    |   56 +
 .../odata-cache-filter-functional-tests.js      |  416 ++
 datajs/tests/odata-cache-fperf-tests.html       |   54 +
 datajs/tests/odata-cache-fperf-tests.js         |  103 +
 datajs/tests/odata-cache-functional-tests.html  |   56 +
 datajs/tests/odata-cache-functional-tests.js    |  611 +++
 .../tests/odata-cache-rx-functional-tests.html  |   54 +
 datajs/tests/odata-cache-rx-functional-tests.js |   74 +
 datajs/tests/odata-fuzz.html                    |  561 +++
 datajs/tests/odata-handler-tests.js             |  319 ++
 datajs/tests/odata-json-light-tests.js          | 2479 +++++++++
 datajs/tests/odata-json-tests.js                |  888 ++++
 datajs/tests/odata-links-functional-tests.html  |   44 +
 datajs/tests/odata-links-functional-tests.js    |  232 +
 ...ata-metadata-awareness-functional-tests.html |   43 +
 ...odata-metadata-awareness-functional-tests.js |  227 +
 datajs/tests/odata-metadata-tests.js            |  486 ++
 datajs/tests/odata-net-tests.js                 |  286 ++
 datajs/tests/odata-perf-tests.html              |   45 +
 datajs/tests/odata-perf-tests.js                |  229 +
 datajs/tests/odata-qunit-tests.htm              |   98 +
 ...odata-read-crossdomain-functional-tests.html |   53 +
 .../odata-read-crossdomain-functional-tests.js  |  137 +
 datajs/tests/odata-read-functional-tests.html   |   45 +
 datajs/tests/odata-read-functional-tests.js     |  583 +++
 .../tests/odata-request-functional-tests.html   |   44 +
 datajs/tests/odata-request-functional-tests.js  |  386 ++
 .../tests/odata-roundtrip-functional-tests.js   |  374 ++
 datajs/tests/odata-tests.js                     |  306 ++
 datajs/tests/odata-xml-tests.js                 |  259 +
 datajs/tests/run-tests.wsf                      |  427 ++
 datajs/tests/store-indexeddb-tests.js           |  246 +
 datajs/tests/store-tests.js                     |  684 +++
 datajs/tests/test-list.js                       |   20 +
 datajs/tests/test-manager.html                  |   88 +
 73 files changed, 24214 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/Gruntfile.js
----------------------------------------------------------------------
diff --git a/datajs/Gruntfile.js b/datajs/Gruntfile.js
index 4c98506..9053bc7 100644
--- a/datajs/Gruntfile.js
+++ b/datajs/Gruntfile.js
@@ -58,21 +58,21 @@ module.exports = function(grunt) {
         https: true,
         port: 44355,
         rejectUnauthorized: false, 
-      },{
+      }/*,{
         context: "/tests/endpoints/",  // When the url contains this...
         host: "localhost",
         changeOrigin: true,
         https: false,
-        port: 10092,
+        port: 46541,
         rejectUnauthorized: false, 
       },{
         context: "/tests/common/",  // When the url contains this...
         host: "localhost",
         changeOrigin: true,
         https: false,
-        port: 10092,
+        port: 46541,
         rejectUnauthorized: false, 
-      }],
+      }*/],
       demo: {
         options: {
           port: 4001 ,
@@ -88,6 +88,21 @@ module.exports = function(grunt) {
           },
         },
       },
+      test: {
+        options: {
+          port: 4002 ,
+          hostname: "localhost",
+          base: "",
+          keepalive : true,
+          middleware: function (connect, options) {
+            return [
+              require("grunt-connect-proxy/lib/utils").proxyRequest ,
+              connect.static(options.base),   // static content
+              connect.directory(options.base) // browse directories
+            ];
+          },
+        },
+      },
     },
     open: {
       demo: {
@@ -111,5 +126,6 @@ module.exports = function(grunt) {
   // Default task.
   grunt.registerTask('build', ['browserify:datajs', 'copy:toDemo']);
   grunt.registerTask('run', ['configureProxies', 'connect:demo']);
+  grunt.registerTask('test', ['configureProxies', 'connect:test']);
 };
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/src/index.js
----------------------------------------------------------------------
diff --git a/datajs/src/index.js b/datajs/src/index.js
index 2079d66..6c6e28f 100644
--- a/datajs/src/index.js
+++ b/datajs/src/index.js
@@ -1,6 +1,6 @@
 
 
-window.dataJS = require('./lib/datajs.js');
+window.datajs = require('./lib/datajs.js');
 window.OData = require('./lib/odata.js');
 
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/src/lib/odata.js
----------------------------------------------------------------------
diff --git a/datajs/src/lib/odata.js b/datajs/src/lib/odata.js
index 60d6105..7e856bf 100644
--- a/datajs/src/lib/odata.js
+++ b/datajs/src/lib/odata.js
@@ -50,7 +50,7 @@ var metadataParser = odataMetadata.metadataParser;
 // CONTENT START
 
 // to do: disable atom scenario
-var handlers = [odata.jsonHandler/*, odata.atomHandler*/, odata.xmlHandler, odata.textHandler];
+var handlers = [odataJson.jsonHandler/*, odata.atomHandler*/, odataXml.xmlHandler, odataHandler.textHandler];
 
 var dispatchHandler = function (handlerMethod, requestOrResponse, context) {
     /// <summary>Dispatches an operation to handlers.</summary>
@@ -148,7 +148,7 @@ exports.request = function (request, success, error, handler, httpClient, metada
         callbackParameterName: request.callbackParameterName,
         formatQueryString: request.formatQueryString,
         enableJsonpCallback: request.enableJsonpCallback,
-s    };
+    };
 
     try {
         odataUtils.prepareRequest(request, handler, context);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/cache-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/cache-tests.js b/datajs/tests/cache-tests.js
new file mode 100644
index 0000000..13fd4b9
--- /dev/null
+++ b/datajs/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


[11/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/common/CacheOracle.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/CacheOracle.js b/datajs/tests/common/CacheOracle.js
new file mode 100644
index 0000000..2130f82
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/common/Instrument.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/Instrument.js b/datajs/tests/common/Instrument.js
new file mode 100644
index 0000000..acbb2b0
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/common/Instrument.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/common/Instrument.svc b/datajs/tests/common/Instrument.svc
new file mode 100644
index 0000000..3d14b16
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/common/ODataReadOracle.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/ODataReadOracle.js b/datajs/tests/common/ODataReadOracle.js
new file mode 100644
index 0000000..4114745
--- /dev/null
+++ b/datajs/tests/common/ODataReadOracle.js
@@ -0,0 +1,205 @@
+// 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.
+
+// Client for the odata.read oracle service
+
+(function (window, undefined) {
+    var jsonMime = "application/json";
+    var universalMime = "*/*";
+    var atomMime = "application/atom+xml";
+
+    var readFeed = function (url, success, mimeType, recognizeDates) {
+        /// <summary>Calls the ReadFeed endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the feed from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME media type in the Accept header</param>
+        var readMethod = getReadMethod(mimeType, "ReadFeed");
+        oracleRequest("GET", readMethod, typeof url === "string" ? { url: url} : url, mimeType, recognizeDates, function (data) {
+            success(data);
+        });
+    };
+
+    var readEntry = function (url, success, mimeType, recognizeDates) {
+        /// <summary>Calls the ReadEntry endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the entry from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME media type in the Accept header</param>
+        var readMethod = getReadMethod(mimeType, "ReadEntry");
+        oracleRequest("GET", readMethod, typeof url === "string" ? { url: url} : url, mimeType, recognizeDates, success);
+    };
+
+    var readFeedLoopback = function (atomFeedXml, success, recognizeDates) {
+        /// <summary>Calls the ReadFeedLoopback endpoint with the specified atom feed xml</summary>
+        /// <param name="atomFeedXml" type="String">The atom feed xml</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        oracleRequest("POST", "ReadFeedLoopback", atomFeedXml, atomMime, recognizeDates, success);
+    };
+
+    var readEntryLoopback = function (atomEntryXml, success, recognizeDates) {
+        /// <summary>Calls the ReadEntryLoopback endpoint with the specified atom entry xml</summary>
+        /// <param name="atomEntryXml" type="String">The atom entry xml</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        oracleRequest("POST", "ReadEntryLoopback", atomEntryXml, atomMime, recognizeDates, success);
+    };
+
+    var readLinksEntry = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        readJson(
+            url,
+            success
+        );
+    };
+
+    var readLinksFeed = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        readJson(
+            url,
+            function (data) {
+                success(data);
+            }
+        );
+    };
+
+    var readMetadata = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        oracleRequest("GET", "ReadMetadata", typeof url === "string" ? { url: url} : url, null, null, success);
+    };
+
+    var readServiceDocument = function (url, success, mimeType) {
+        /// <summary>Calls the ReadServiceDocument endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to the service</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME type being tested</param>
+        var readMethod = getReadMethod(mimeType, "ReadServiceDocument");
+        oracleRequest("GET", readMethod, typeof url === "string" ? { url: url} : url, mimeType, null, success);
+    };
+
+    var readJson = function (url, success) {
+        $.ajax({
+            url: url,
+            accepts: null,
+            dataType: "json",
+            beforeSend: function (xhr) {
+                xhr.setRequestHeader("Accept", jsonMime);
+                xhr.setRequestHeader("OData-MaxVersion", "4.0");
+            },
+            success: function (data) {
+                success(data);
+            }
+        });
+    };
+
+    var readJsonAcrossServerPages = function (url, success) {
+        var data = {};
+        var readPage = function (url) {
+            readJson(url, function (feedData) {
+                var nextLink = feedData["@odata.nextLink"];
+                if (nextLink) {
+                    var index = url.indexOf(".svc/", 0);
+                    if (index != -1) {
+                        nextLink = url.substring(0, index + 5) + nextLink;
+                    }
+                }
+
+                if (data.value && feedData.value) {
+                    data.value = data.value.concat(feedData.value);
+                }
+                else {
+                    for (var property in feedData) {
+                        if (property != "@odata.nextLink") {
+                            data[property] = feedData[property];
+                        }
+                    }
+                }
+
+                if (nextLink) {
+                    readPage(nextLink);
+                }
+                else {
+                    success(data);
+                }
+            });
+        };
+
+        readPage(url);
+    };
+
+    var getReadMethod = function (mimeType, defaultEndpoint) {
+        switch (mimeType) {
+            case atomMime:
+                return defaultEndpoint;
+            case jsonMime:
+            case universalMime:
+            default:
+                return "ReadJson";
+        }
+    };
+
+    var oracleRequest = function (method, endpoint, data, mimeType, recognizeDates, success) {
+        /// <summary>Requests a JSON object from the oracle service, removing WCF-specific artifacts</summary>
+        /// <param name="method" type="String">The HTTP method (GET or POST)</param>
+        /// <param name="endpoint" type="String">The oracle endpoint</param>
+        /// <param name="data" type="Object">The data to send with the request</param>
+        /// <param name="reviver" type="Function">The reviver function to run on each deserialized object</param>
+        /// <param name="success" type="Function">Success callback</param>
+        var url = "./common/ODataReadOracle.svc/" + endpoint;
+        if (mimeType) {
+            data.mimeType = mimeType;
+        }
+
+        $.ajax({
+            type: method,
+            url: url,
+            data: data,
+            dataType: "text",
+            success: function (data) {
+                var json = JSON.parse(data);
+                success(json);
+            }
+        });
+    };
+
+    var removeProperty = function (data, property) {
+        /// <summary>Removes the specified property recursively from the given object</summary>
+        /// <param name="data" type="Object">The object to operate on</param>
+        /// <param name="property" type="String">The name of the property to remove</param>
+        if (typeof data === "object" && data !== null) {
+            if (data[property]) {
+                delete data[property];
+            }
+
+            for (prop in data) {
+                removeProperty(data[prop], property);
+            }
+        }
+    };
+
+    window.ODataReadOracle = {
+        readFeed: readFeed,
+        readEntry: readEntry,
+        readFeedLoopback: readFeedLoopback,
+        readEntryLoopback: readEntryLoopback,
+        readLinksEntry: readLinksEntry,
+        readLinksFeed: readLinksFeed,
+        readJson: readJson,
+        readJsonAcrossServerPages: readJsonAcrossServerPages,
+        readMetadata: readMetadata,
+        readServiceDocument: readServiceDocument
+    };
+})(window);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/common/ODataReadOracle.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/common/ODataReadOracle.svc b/datajs/tests/common/ODataReadOracle.svc
new file mode 100644
index 0000000..51ccd62
--- /dev/null
+++ b/datajs/tests/common/ODataReadOracle.svc
@@ -0,0 +1,189 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
+    Service="DataJS.Tests.ODataReadOracle" %>
+
+// 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.
+
+//uncomment this line to debug JSON serialization.
+//#define DEBUG_SERIALIZATION
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.Net;
+    using System.Runtime.Serialization;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Syndication;
+    using System.ServiceModel.Web;
+    using System.Xml;
+    using System.Xml.Linq;
+    using Microsoft.Spatial;
+    using Microsoft.OData.Core;
+    using System.Web.Script.Serialization;
+
+    /// <summary>
+    /// Oracle for the OData.read library function
+    /// </summary>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class ODataReadOracle
+    {
+        const string jsonlightMediaType = "application/json";
+
+        /// <summary>
+        /// Reads a URI that will return an OData ATOM feed
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadFeed(string url, string user, string password)
+        {
+            WebResponse response = ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password).GetResponse();
+            return AtomReader.ReadFeed(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return an OData ATOM feed entry
+        /// </summary>
+        /// <param name="url">URL of the entry</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>JSON object expected to be returned by OData.read</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadEntry(string url, string user, string password)
+        {
+            WebResponse response = ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password).GetResponse();
+            return AtomReader.ReadEntry(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return a metadata object
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <returns>Stream of metadata in json light format</returns>
+        [OperationContract]
+        [WebGet]
+        public Stream ReadMetadata(string url)
+        {
+            WebResponse response = WebRequest.Create(ResolveUri(url, UriKind.Absolute)).GetResponse();
+            Dictionary<string, object> jsonObject = CsdlReader.ReadCsdl(new StreamReader(response.GetResponseStream()));
+            return ReaderUtils.ConvertDictionarytoJsonlightStream(jsonObject);
+        }
+
+        /// <summary>
+        /// Reads a URI that will return a metadata object
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <param name="mimeType">Mime type being tested to determine base URI</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadServiceDocument(string url, string mimeType)
+        {
+            WebResponse response = WebRequest.Create(ResolveUri(url, UriKind.Absolute)).GetResponse();
+            string baseUri = string.Empty;
+
+            // With JSON responses only relative path passed to the library is available
+            if (mimeType.Equals(jsonlightMediaType))
+            {
+                baseUri = ResolveUri(url, UriKind.Relative).ToString();
+            }
+            else
+            {
+                baseUri = response.ResponseUri.AbsoluteUri;
+            }
+
+            return AtomReader.ReadServiceDocument(new StreamReader(response.GetResponseStream()), baseUri);
+        }
+
+        /// <summary>
+        /// Reads a URI that will get the Json response and return the stream
+        /// </summary>
+        /// <param name="url">URL of the entry</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>Stream of the Json response expected to be returned by OData.read</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public Stream ReadJson(string url, string mimeType, string user, string password)
+        {
+            if (mimeType == null)
+            {
+                mimeType = jsonlightMediaType + ";odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8";
+            }
+            
+            HttpWebRequest request = (HttpWebRequest)ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password);
+            request.Accept = mimeType;
+            WebResponse response = request.GetResponse();
+
+            return response.GetResponseStream();
+        }
+
+
+        /// <summary>
+        /// Loops back an ATOM feed passed to the webservice in JSON format.
+        /// </summary>
+        /// <param name="content">The ATOM feed xml stream to loopback as JSON</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadFeedLoopback(Stream content)
+        {
+            return AtomReader.ReadFeed(new StreamReader(content));
+        }
+
+        /// <summary>
+        /// Loops back an ATOM entry passed to the webservice in JSON format.
+        /// </summary>
+        /// <param name="content">The ATOM entry xml stream to loopback as JSON</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadEntryLoopback(Stream content)
+        {
+            return AtomReader.ReadEntry(new StreamReader(content));
+        }
+
+        /// <summary>
+        /// Resolves the given url string to a URI
+        /// </summary>
+        /// <param name="url">The given URL string</param>
+        /// <param name="urlKind">URI kind to resolve to</param>
+        /// <returns>The resolved URI</returns>
+        private static string ResolveUri(string url, UriKind uriKind)
+        {
+            Uri resolvedUri = new Uri(url, UriKind.RelativeOrAbsolute);
+            if (!resolvedUri.IsAbsoluteUri)
+            {
+                // If the given URI is relative, then base it on the Referer URI
+                Uri baseUri = new Uri(WebOperationContext.Current.IncomingRequest.Headers["Referer"]);
+                resolvedUri = new Uri(baseUri, resolvedUri);
+                if (uriKind == UriKind.Relative)
+                {
+                    resolvedUri = baseUri.MakeRelativeUri(resolvedUri);
+                }
+            }
+
+            return resolvedUri.ToString();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/common/ObservableHttpClient.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/ObservableHttpClient.js b/datajs/tests/common/ObservableHttpClient.js
new file mode 100644
index 0000000..2b7fe98
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/common/TestLogger.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/common/TestLogger.svc b/datajs/tests/common/TestLogger.svc
new file mode 100644
index 0000000..d236b24
--- /dev/null
+++ b/datajs/tests/common/TestLogger.svc
@@ -0,0 +1,846 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="DataJS.Tests.TestSynchronizerFactory" Service="DataJS.Tests.TestSynchronizer" %>
+
+// 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.Linq;
+    using System.Net;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Channels;
+    using System.ServiceModel.Description;
+    using System.ServiceModel.Dispatcher;
+    using System.ServiceModel.Web;
+    using System.Text;
+    using System.Threading;
+    using System.Xml;
+    
+    /// <summary>
+    /// This factory supports reconfiguring the service to allow incoming messages
+    /// to be larger than the default.
+    /// </summary>
+    public class TestSynchronizerFactory : WebScriptServiceHostFactory
+    {
+        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
+        {
+            var result = base.CreateServiceHost(serviceType, baseAddresses);
+            result.Opening += ServiceHostOpening;
+            return result;
+        }
+
+        private static void UpdateService(ServiceDescription description)
+        {
+            const long LargeMaxReceivedMessageSize = 1024 * 1024 * 16;
+            foreach (var endpoint in description.Endpoints)
+            {
+                var basic = endpoint.Binding as BasicHttpBinding;
+                if (basic != null)
+                {
+                    basic.MaxReceivedMessageSize = LargeMaxReceivedMessageSize;
+                }
+
+                var http = endpoint.Binding as WebHttpBinding;
+                if (http != null)
+                {
+                    http.MaxReceivedMessageSize = LargeMaxReceivedMessageSize;
+                }
+            }
+        }
+
+        private void ServiceHostOpening(object sender, EventArgs e)
+        {
+            UpdateService((sender as ServiceHost).Description);
+        }        
+    }
+
+    /// <summary>Use this class to log test activity.</summary>
+    /// <remarks>
+    /// A test run can be created by invoking CreateTestRun. With a test
+    /// run ID, the following operations can be invoked:
+    ///
+    /// - AddTestPages: adds test pages to be made available to future callers (typically to support tests from different files)
+    /// - SetTestNamePrefix: sets a string that will be prefixed to every test name in logs (typically to include a browser name)
+    /// - MarkInProgress: resets the test run to "in-progress" for another variation (typically to run a different browser)
+    /// - IsTestRunInProgress: checks whether it's still in progress
+    /// - GetTestRunResults: returns the test results in TRX format
+    /// - LogAssert: logs a single assertion in the current test for the run
+    /// - LogTestStart: logs a test that has begun execution
+    /// - LogTestDone: logs a test that has ended execution
+    /// - TestCompleted: logs that a test has completed execution; returns the next page with tests or an empty string
+    /// </remarks>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class TestSynchronizer
+    {
+        private static readonly Dictionary<string, TestRunContext> testRuns = new Dictionary<string, TestRunContext>();
+        private const string Inconclusive = "Inconclusive";
+        private const string InProgress = "InProgress";
+        private const string Failed = "Failed";
+        private const string Passed = "Passed";
+        private const string Completed = "Completed";
+        
+        /// <summary>
+        /// Adds test pages to the specified runs; replaces existing files (helps with reliablity when something
+        /// fails part-way).
+        /// </summary>
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public int AddTestPages(string testRunId, string pages, string filter)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestPages.Clear();
+                context.TestPages.AddRange(pages.Split(',').Select(page => page + "?testRunId=" + testRunId + (filter == null ? string.Empty : "?filter=" + filter)));
+                return context.TestPages.Count;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public string CreateTestRun()
+        {
+            DisableResponseCaching();
+
+            Guid value = Guid.NewGuid();
+            string result = value.ToString();
+            TestRunContext context = CreateTestRunContextWithId(value);
+            
+            lock (testRuns)
+            {
+                testRuns.Add(result, context);
+            }
+
+            return result;
+        }
+
+        /// <summary>Checks whether the test run is in progress.</summary>
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public bool IsTestRunInProgress(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            return context.TestRun.ResultSummary.Outcome == InProgress;
+        }
+        
+        /// <summary>Provides a list of all test runs being tracked.</summary>
+        [OperationContract]
+        [WebGet(ResponseFormat=WebMessageFormat.Json)]
+        public IEnumerable<string> GetActiveTestRuns()
+        {
+            DisableResponseCaching();
+
+            List<string> result;
+            lock (testRuns)
+            {
+                result = new List<string>(testRuns.Keys);
+            }
+            
+            return result;
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Xml)]
+        public Message GetTestRunResults(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                this.CompleteTestRun(run);
+
+                TestRunXmlBodyWriter writer = new TestRunXmlBodyWriter(run);
+                return Message.CreateMessage(
+                    MessageVersion.None,
+                    OperationContext.Current.OutgoingMessageHeaders.Action,
+                    writer);
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogAssert(string testRunId, bool pass, string message, string name, string actual, string expected)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                TestResult result = run.TestResults.LastOrDefault(r => r.TestName == prefixedName);
+                if (result == null)
+                {
+                    throw new InvalidOperationException("Unable to find test " + prefixedName + " in run " + testRunId);
+                }
+                
+                result.DebugTrace.AppendLine(message);
+                if (!pass)
+                {
+                    result.ErrorMessages.AppendLine(message);
+                    result.ErrorMessages.AppendLine("Expected: " + expected);
+                    result.ErrorMessages.AppendLine("Actual: " + actual);
+                }
+            }
+        }
+        
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebInvoke(ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
+        public void LogBatch(string[] urls)
+        {
+            DisableResponseCaching();
+            
+            foreach (var url in urls)
+            {
+                Uri parsed = new Uri(OperationContext.Current.Channel.LocalAddress.Uri, url);
+                string methodName = parsed.Segments[parsed.Segments.Length - 1];
+                System.Reflection.MethodInfo method = this.GetType().GetMethod(methodName);
+                System.Reflection.ParameterInfo[] parameterInfos = method.GetParameters();
+                object[] parameters = new object[parameterInfos.Length];
+                System.Collections.Specialized.NameValueCollection query = System.Web.HttpUtility.ParseQueryString(parsed.Query);
+                for (int i = 0; i < parameters.Length; i++)
+                {
+                    object value = query[parameterInfos[i].Name];
+                    parameters[i] = Convert.ChangeType(value, parameterInfos[i].ParameterType, System.Globalization.CultureInfo.InvariantCulture);
+                }
+                
+                method.Invoke(this, parameters);
+            }            
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogTestStart(string testRunId, string name, DateTime startTime)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                Guid testId = Guid.NewGuid();
+                Guid executionId = Guid.NewGuid();
+                Guid testListId = run.TestLists.Single().Id;
+                run.TestDefinitions.Add(new TestDefinition()
+                {
+                    Id = testId,
+                    Name = prefixedName,
+                    ExecutionId = executionId,
+                });
+                run.TestEntries.Add(new TestEntry()
+                {
+                    TestId = testId,
+                    ExecutionId = executionId,
+                    TestListId = testListId
+                });
+                run.TestResults.Add(new TestResult()
+                {
+                    ExecutionId = executionId,
+                    TestId = testId,
+                    TestListId = testListId,
+                    TestName = prefixedName,
+                    ComputerName = Environment.MachineName,
+                    StartTime = startTime,
+                    EndTime = startTime,
+                    TestType = Guid.Parse("13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"),
+                    Outcome = InProgress,
+                    // RelativeResultsDirectory?
+                });
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogTestDone(string testRunId, string name, int failures, int total, DateTime endTime)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                TestResult result = run.TestResults.LastOrDefault(r => r.TestName == prefixedName);
+                if (failures > 0)
+                {
+                    result.Outcome = Failed;
+                }
+                else
+                {
+                    result.Outcome = Passed;
+                }
+
+                result.EndTime = endTime;
+            }
+        }
+
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void MarkInProgress(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestRun.ResultSummary.Outcome = InProgress;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void SetTestNamePrefix(string testRunId, string prefix)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestNamePrefix = prefix;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public string TestCompleted(string testRunId, int failures, int total)
+        {
+            DisableResponseCaching();
+            
+            var context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                string result;
+                if (context.TestPages.Count == 0)
+                {
+                    context.TestRun.ResultSummary.Outcome = Completed;
+                    result = "";
+                }
+                else
+                {
+                    result = context.TestPages[0];
+                    context.TestPages.RemoveAt(0);
+                }
+
+                return result;
+            }
+        }
+
+        private static TestRunContext CreateTestRunContextWithId(Guid value)
+        {
+            TestRun run = new TestRun();
+            run.Id = value;
+            run.Name = "Test run";
+            run.TestTimes.Creation = DateTime.Now;
+            run.TestTimes.Queueing = DateTime.Now;
+            run.TestLists.Add(new TestList()
+            {
+                Name = "All Results",
+                Id = Guid.NewGuid()
+            });
+
+            // For the time being, set up a fake test settings.
+            run.TestSettings.Id = Guid.NewGuid();
+
+            run.ResultSummary.Outcome = InProgress;
+
+            TestRunContext context = new TestRunContext();
+            context.TestRun = run;
+
+            return context;
+        }
+
+        private static void DisableResponseCaching()
+        {
+            WebOperationContext.Current.OutgoingResponse.Headers[HttpResponseHeader.CacheControl] = "no-cache";            
+        }
+
+        private static TestRunContext GetTestRunContext(string testRunId)
+        {
+            if (testRunId == null)
+            {
+                throw new ArgumentNullException("testRunId");
+            }
+            
+            lock (testRuns)
+            {
+                // For an 0-filled GUID, allow create-on-demand to simplify ad-hoc testing.
+                // Something like:
+                // http://localhost:8989/tests/odata-qunit-tests.htm?testRunId=00000000-0000-0000-0000-000000000000
+                if (!testRuns.ContainsKey(testRunId))
+                {
+                    Guid value = Guid.Parse(testRunId);
+                    if (value == Guid.Empty)
+                    {
+                        TestRunContext context = CreateTestRunContextWithId(value);
+                        testRuns.Add(testRunId, context);
+                    }
+                }
+                
+                return testRuns[testRunId];
+            }
+        }
+
+        private void CompleteTestRun(TestRun run)
+        {
+            run.TestTimes.Finish = DateTime.Now;
+
+            // Fill counts in result object.
+            var summary = run.ResultSummary;
+            summary.Executed = 0;
+            summary.Error = 0;
+            summary.Failed = 0;
+            summary.Timeout = 0;
+            summary.Aborted = 0;
+            summary.Inconclusive = 0;
+            summary.PassedButRunAborted = 0;
+            summary.NotRunnable = 0;
+            summary.NotExecuted = 0;
+            summary.Disconnected = 0;
+            summary.Warning = 0;
+            summary.Passed = 0;
+            summary.Completed = 0;
+            summary.InProgress = 0;
+            summary.Pending = 0;
+
+            foreach (var testResult in run.TestResults)
+            {
+                string outcome = testResult.Outcome;
+                switch (outcome)
+                {
+                    case InProgress:
+                        summary.Executed++;
+                        summary.InProgress++;
+                        break;
+                    case Failed:
+                        summary.Executed++;
+                        summary.Completed++;
+                        summary.Failed++;
+                        break;
+                    case Passed:
+                        summary.Executed++;
+                        summary.Completed++;
+                        summary.Passed++;
+                        break;
+                    default:
+                        summary.Failed++;
+                        break;
+                }
+            }
+
+            summary.Total = run.TestResults.Count;
+
+            if (summary.Failed != 0)
+            {
+                summary.Outcome = Failed;
+            }
+            else if (summary.Total <= 0 || summary.Passed < summary.Total)
+            {
+                summary.Outcome = Inconclusive;
+            }
+            else
+            {
+                summary.Outcome = Passed;
+            }
+        }
+    }
+
+    public class TestRunContext
+    {
+        public TestRunContext()
+        {
+            this.TestPages = new List<string>();
+        }
+        
+        public TestRun TestRun { get; set; }
+        public string TestNamePrefix { get; set; }
+        public List<string> TestPages { get; set; }
+    }
+
+    public class TestResultWriter
+    {
+        private const string TestNamespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010";
+
+        public static void WriteTestRun(TestRun run, string path)
+        {
+            if (run == null)
+            {
+                throw new ArgumentNullException("run");
+            }
+            if (path == null)
+            {
+                throw new ArgumentNullException("path");
+            }
+
+            using (XmlWriter writer = XmlWriter.Create(path))
+            {
+                WriteTestRun(run, path);
+            }
+        }
+
+        public static void WriteTestRun(TestRun run, XmlWriter writer)
+        {
+            if (run == null)
+            {
+                throw new ArgumentNullException("run");
+            }
+            if (writer == null)
+            {
+                throw new ArgumentNullException("writer");
+            }
+
+            writer.WriteStartElement("TestRun", TestNamespace);
+            writer.WriteGuidIfPresent("id", run.Id);
+            writer.WriteAttributeString("name", run.Name);
+            writer.WriteAttributeString("runUser", run.RunUser);
+
+            WriteTestSettings(run.TestSettings, writer);
+            WriteResultSummary(run.ResultSummary, writer);
+
+            // Write test definitions.
+            writer.WriteStartElement("TestDefinitions", TestNamespace);
+            foreach (var definition in run.TestDefinitions)
+            {
+                WriteTestDefinition(definition, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test lists.
+            writer.WriteStartElement("TestLists", TestNamespace);
+            foreach (var list in run.TestLists)
+            {
+                WriteTestList(list, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test entries.
+            writer.WriteStartElement("TestEntries", TestNamespace);
+            foreach (var entry in run.TestEntries)
+            {
+                WriteTestEntry(entry, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test results.
+            writer.WriteStartElement("Results", TestNamespace);
+            foreach (var result in run.TestResults)
+            {
+                WriteTestResults(result, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Close the test run element.
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestResults(TestResult result, XmlWriter writer)
+        {
+            if (result == null)
+            {
+                throw new ArgumentNullException("result");
+            }
+
+            writer.WriteStartElement("UnitTestResult", TestNamespace);
+            writer.WriteGuidIfPresent("testId", result.TestId);
+            writer.WriteGuidIfPresent("testListId", result.TestListId);
+            writer.WriteGuidIfPresent("executionId", result.ExecutionId);
+            writer.WriteGuidIfPresent("RelativeResultsDirectory", result.RelativeResultsDirectory);
+
+            writer.WriteAttributeString("testName", result.TestName);
+            writer.WriteAttributeString("computerName", result.ComputerName);
+            writer.WriteAttributeString("duration", result.Duration.ToString());
+            writer.WriteAttributeString("startTime", XmlConvert.ToString(result.StartTime));
+            writer.WriteAttributeString("endTime", XmlConvert.ToString(result.EndTime));
+            writer.WriteAttributeString("outcome", result.Outcome);
+
+            writer.WriteGuidIfPresent("testType", result.TestType);
+
+            if (result.DebugTrace.Length > 0)
+            {
+                writer.WriteStartElement("Output");
+
+                writer.WriteStartElement("DebugTrace");
+                writer.WriteString(result.DebugTrace.ToString());
+                writer.WriteEndElement();
+
+                writer.WriteStartElement("ErrorInfo");
+                writer.WriteStartElement("Message");
+                writer.WriteString(result.ErrorMessages.ToString());
+                writer.WriteEndElement();
+                writer.WriteEndElement();
+
+                writer.WriteEndElement();
+            }
+            
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestEntry(TestEntry entry, XmlWriter writer)
+        {
+            if (entry == null)
+            {
+                throw new ArgumentNullException("entry");
+            }
+
+            writer.WriteStartElement("TestEntry", TestNamespace);
+            writer.WriteGuidIfPresent("testId", entry.TestId);
+            writer.WriteGuidIfPresent("testListId", entry.TestListId);
+            writer.WriteGuidIfPresent("executionId", entry.ExecutionId);
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestList(TestList list, XmlWriter writer)
+        {
+            if (list == null)
+            {
+                throw new ArgumentNullException("list");
+            }
+
+            writer.WriteStartElement("TestList", TestNamespace);
+            writer.WriteAttributeString("name", list.Name);
+            writer.WriteGuidIfPresent("id", list.Id);
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestDefinition(TestDefinition definition, XmlWriter writer)
+        {
+            if (definition == null)
+            {
+                throw new ArgumentNullException("definition");
+            }
+
+            writer.WriteStartElement("UnitTest", TestNamespace);
+            writer.WriteAttributeString("name", definition.Name);
+            writer.WriteAttributeString("storage", definition.Storage);
+            writer.WriteGuidIfPresent("id", definition.Id);
+            
+            // There are more thing we could write here: DeploymentItems, Execution, TestMethod
+            
+            // This is the minimum needed to load the test in the IDE.
+            writer.WriteStartElement("Execution", TestNamespace);
+            writer.WriteGuidIfPresent("id", definition.ExecutionId);
+            writer.WriteEndElement();
+
+            writer.WriteStartElement("TestMethod", TestNamespace);
+            writer.WriteAttributeString("codeBase", "fake-test-file.js");
+            writer.WriteAttributeString("adapterTypeName", "Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestAdapter, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.Adapter, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
+            writer.WriteAttributeString("className", "FakeClassName, TestLogging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
+            writer.WriteAttributeString("name", definition.Name);
+            writer.WriteEndElement();
+            
+            writer.WriteEndElement();
+        }
+
+        private static void WriteResultSummary(ResultSummary resultSummary, XmlWriter writer)
+        {
+            if (resultSummary == null)
+            {
+                throw new ArgumentNullException("resultSummary");
+            }
+
+            writer.WriteStartElement("ResultSummary", TestNamespace);
+            writer.WriteAttributeString("outcome", resultSummary.Outcome);
+
+            writer.WriteStartElement("Counters", TestNamespace);
+            
+            foreach (var p in typeof(ResultSummary).GetProperties())
+            {
+                if (p.PropertyType != typeof(int))
+                {
+                    continue;
+                }
+
+                int value = (int)p.GetValue(resultSummary, null);
+                string attributeName = p.Name;
+                attributeName = attributeName.Substring(0, 1).ToLowerInvariant() +  attributeName.Substring(1);
+                writer.WriteAttributeString(attributeName, value.ToString());
+            }
+            
+            writer.WriteEndElement();
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestSettings(TestSettings testSettings, XmlWriter writer)
+        {
+            if (testSettings == null)
+            {
+                throw new ArgumentNullException("testSettings");
+            }
+            
+            writer.WriteStartElement("TestSettings", TestNamespace);
+            writer.WriteAttributeString("name", testSettings.Name);
+            writer.WriteGuidIfPresent("id", testSettings.Id);
+            // There are more things we could write here.
+            writer.WriteEndElement();
+        }
+    }
+
+    public static class XmlWriterExtensions
+    {
+        public static void WriteGuidIfPresent(this XmlWriter writer, string attributeName, Guid value)
+        {
+            if (value != Guid.Empty)
+            {
+                writer.WriteAttributeString(attributeName, value.ToString());
+            }
+        }
+    }
+
+    public class TestRun
+    {
+        public TestRun()
+        {
+            this.TestDefinitions = new List<TestDefinition>();
+            this.TestLists = new List<TestList>();
+            this.TestEntries = new List<TestEntry>();
+            this.TestResults = new List<TestResult>();
+            this.ResultSummary = new ResultSummary();
+            this.TestTimes = new TestTimes();
+            this.TestSettings = new TestSettings();
+            
+            this.Id = Guid.NewGuid();
+            this.RunUser = Environment.UserDomainName + "\\" + Environment.UserName;
+        }
+
+        public Guid Id { get; set; }
+        public string Name { get; set; }
+        public string RunUser { get; set; }
+        public TestSettings TestSettings { get; set; }
+        public TestTimes TestTimes { get; set; }
+        public ResultSummary ResultSummary { get; set; }
+        public List<TestDefinition> TestDefinitions { get; set; }
+        public List<TestList> TestLists { get; set; }
+        public List<TestEntry> TestEntries { get; set; }
+        public List<TestResult> TestResults { get; set; }
+    }
+
+    public class TestSettings
+    {
+        public Guid Id { get; set; }
+        public string Name { get; set; }
+    }
+
+    public class TestTimes
+    {
+        public DateTime Creation { get; set; }
+        public DateTime Queueing { get; set; }
+        public DateTime Start { get; set; }
+        public DateTime Finish { get; set; }
+    }
+
+    public class ResultSummary
+    {
+        public string Outcome { get; set; }
+        public int Total { get; set; }
+        public int Executed { get; set; }
+        public int Error { get; set; }
+        public int Failed { get; set; }
+        public int Timeout { get; set; }
+        public int Aborted { get; set; }
+        public int Inconclusive { get; set; }
+        public int PassedButRunAborted { get; set; }
+        public int NotRunnable { get; set; }
+        public int NotExecuted { get; set; }
+        public int Disconnected { get; set; }
+        public int Warning { get; set; }
+        public int Passed { get; set; }
+        public int Completed { get; set; }
+        public int InProgress { get; set; }
+        public int Pending { get; set; }
+    }
+
+    public class TestDefinition
+    {
+        public string Name { get; set; }
+        public string Storage { get; set; }
+        public Guid Id { get; set; }
+        public Guid ExecutionId { get; set; }
+    }
+
+    public class TestList
+    {
+        public string Name { get; set; }
+        public Guid Id { get; set; }
+    }
+
+    public class TestEntry
+    {
+        public Guid TestId { get; set; }
+        public Guid ExecutionId { get; set; }
+        public Guid TestListId { get; set; }
+    }
+
+    public class TestResult
+    {
+        public TestResult()
+        {
+            this.DebugTrace = new StringBuilder();
+            this.ErrorMessages = new StringBuilder();
+        }
+        
+        public Guid ExecutionId { get; set; }
+        public Guid TestId { get; set; }
+        public string TestName { get; set; }
+        public string ComputerName { get; set; }
+        public TimeSpan Duration { get { return this.EndTime - this.StartTime; } }
+        public DateTime StartTime { get; set; }
+        public DateTime EndTime { get; set; }
+        public Guid TestType { get; set; }
+        public string Outcome { get; set; }
+        public Guid TestListId { get; set; }
+        public Guid RelativeResultsDirectory { get; set; }
+        public StringBuilder DebugTrace { get; set; }
+        public StringBuilder ErrorMessages { get; set; }
+    }
+
+    class TestRunXmlBodyWriter : BodyWriter
+    {
+        private readonly TestRun run;
+
+        public TestRunXmlBodyWriter(TestRun run)
+            : base(true)
+        {
+            this.run = run;
+        }
+
+        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
+        {
+            TestResultWriter.WriteTestRun(this.run, writer);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/common/TestSynchronizerClient.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/TestSynchronizerClient.js b/datajs/tests/common/TestSynchronizerClient.js
new file mode 100644
index 0000000..c758413
--- /dev/null
+++ b/datajs/tests/common/TestSynchronizerClient.js
@@ -0,0 +1,218 @@
+// 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.
+
+// TestSynchronizer Client
+// Use to log assert pass/fails and notify mstest a test has completed execution
+
+(function (window, undefined) {
+    var testRunId = "";
+    var serviceRoot = "./common/TestLogger.svc/";
+    var recording = null;
+    var recordingLength = 0;
+    var maxStringLength = 8192;
+    var maxPostLength = 2097152;
+
+    var callTestSynchronizer = function (methodName, parameterUrl) {
+        /// <summary>Invokes a function on the test synchronizer.</summary>
+        /// <param name="partialUrl" type="String" optional="true">URL to work with.</param>
+        /// <returns type="String">A response from the server, possibly null.</returns>
+        /// <remarks>
+        /// If the recording variable is assigned, then the call is logged
+        /// but nothing is invoked.
+        /// </remarks>
+
+        var partialUrl;
+        if (testRunId) {
+            partialUrl = methodName + "?testRunId=" + testRunId + "&" + parameterUrl;
+        }
+        else {
+            partialUrl = methodName + "?" + parameterUrl;
+        }
+
+        var url = serviceRoot + partialUrl;
+
+        if (recording) {
+            if (url.length > maxStringLength) {
+                url = url.substr(0, maxStringLength);
+            }
+
+            recordingLength += url.length;
+            if (recordingLength > maxPostLength) {
+                submitRecording();
+                recording = [];
+                recordingLength = url.length;
+            }
+
+            recording.push(url);
+            return null;
+        }
+
+        var xhr;
+        if (window.XMLHttpRequest) {
+            xhr = new window.XMLHttpRequest();
+        } else {
+            xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0");
+        }
+
+        xhr.open("GET", url, false);
+        xhr.send();
+        return xhr.responseText;
+    };
+
+    var getLogPrefix = function (result) {
+        /// <summary>Returns the log prefix for a given result</summary>
+        /// <param name="result" type="Boolean">Whether the result is pass or fail. If null, the log line is assumed to be diagnostic</param>
+        return "[" + getShortDate() + "] " + (result === true ? "[PASS] " : (result === false ? "[FAIL] " : ""));
+    };
+
+    var getShortDate = function () {
+        /// <summary>Returns the current date and time formatted as "yyyy-mm-dd hh:mm:ss.nnn".</summary>
+        var padToLength = function (number, length) {
+            var result = number + "";
+            var lengthDiff = length - result.length;
+            for (var i = 0; i < lengthDiff; i++) {
+                result = "0" + result;
+            }
+
+            return result;
+        }
+
+        var date = new Date();
+        var day = padToLength(date.getDate(), 2);
+        var month = padToLength(date.getMonth() + 1, 2);
+        var year = date.getFullYear();
+
+        var hours = padToLength(date.getHours(), 2);
+        var minutes = padToLength(date.getMinutes(), 2);
+        var seconds = padToLength(date.getSeconds(), 2);
+        var milliseconds = padToLength(date.getMilliseconds(), 3);
+
+        return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds + "." + milliseconds;
+    };
+
+    var submitRecording = function () {
+        var body = { urls: recording };
+        postToUrl("LogBatch", body);
+    };
+
+    var postToUrl = function (methodName, body) {
+        /// <summary>POSTs body to the designated methodName.</summary>
+        var xhr;
+        if (window.XMLHttpRequest) {
+            xhr = new window.XMLHttpRequest();
+        } else {
+            xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0");
+        }
+
+        var url = serviceRoot + methodName;
+        xhr.open("POST", url, false);
+        xhr.setRequestHeader("Content-Type", "application/json");
+        xhr.send(window.JSON.stringify(body));
+        if (xhr.status < 200 || xhr.status > 299) {
+            throw { message: "Unable to POST to url.\r\n" + xhr.responseText };
+        }
+
+        return xhr.responseText;
+    }
+
+    function LogAssert(result, message, name, expected, actual) {
+        var parameterUrl = "pass=" + result + "&message=" + encodeURIComponent(message) + "&name=" + encodeURIComponent(name);
+
+        if (!result) {
+            parameterUrl += "&actual=" + encodeURIComponent(actual) + "&expected=" + encodeURIComponent(expected);
+        }
+
+        callTestSynchronizer("LogAssert", parameterUrl);
+    }
+
+    function LogTestStart(name) {
+        callTestSynchronizer("LogTestStart", "name=" + encodeURIComponent(name) + "&startTime=" + encodeURIComponent(getShortDate()));
+    }
+
+    function LogTestDone(name, failures, total) {
+        callTestSynchronizer("LogTestDone", "name=" + encodeURIComponent(name) + "&failures=" + failures + "&total=" + total + "&endTime=" + encodeURIComponent(getShortDate()));
+    }
+
+    function TestCompleted(failures, total) {
+        return callTestSynchronizer("TestCompleted", "failures=" + failures + "&total=" + total);
+    }
+
+    var extractTestRunId = function () {
+        /// <summary>Extracts the testRunId value from the window query string.</summary>
+        /// <returns type="String">testRunId, possibly empty.</returns>
+        var i, len;
+        var uri = window.location.search;
+        if (uri) {
+            var parameters = uri.split("&");
+            for (i = 0, len = parameters.length; i < len; i++) {
+                var index = parameters[i].indexOf("testRunId=");
+                if (index >= 0) {
+                    return parameters[i].substring(index + "testRunId=".length);
+                }
+            }
+        }
+
+        return "";
+    };
+
+    var init = function (qunit) {
+        /// <summary>Initializes the test logger synchronizer.</summary>
+        /// <param name="qunit">Unit testing to hook into.</param>
+        /// <remarks>If there is no testRunId present, the QUnit functions are left as they are.</remarks>
+        var logToConsole = function (context) {
+            if (window.console && window.console.log) {
+                window.console.log(context.result + ' :: ' + context.message);
+            }
+        };
+
+        testRunId = extractTestRunId();
+        if (!testRunId) {
+            qunit.log = logToConsole;
+        } else {
+            recording = [];
+            qunit.log = function (context) {
+                logToConsole(context);
+
+                var name = qunit.config.current.testName;
+                if (!(context.actual && context.expected)) {
+                    context.actual = context.result;
+                    context.expected = true;
+                }
+                LogAssert(context.result, getLogPrefix(context.result) + context.message, name, window.JSON.stringify(context.expected), window.JSON.stringify(context.actual));
+            };
+
+            qunit.testStart = function (context) {
+                LogTestStart(context.name);
+            };
+
+            qunit.testDone = function (context) {
+                LogTestDone(context.name, context.failed, context.total);
+            }
+
+            qunit.done = function (context) {
+                submitRecording();
+                recording = null;
+
+                var nextUrl = TestCompleted(context.failed, context.total);
+                nextUrl = JSON.parse(nextUrl).d;
+                if (nextUrl) {
+                    window.location.href = nextUrl;
+                }
+            }
+        }
+    };
+
+    window.TestSynchronizer = {
+        init: init
+    };
+})(window);
\ No newline at end of file


[04/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-json-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-json-tests.js b/datajs/tests/odata-json-tests.js
new file mode 100644
index 0000000..826a36b
--- /dev/null
+++ b/datajs/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.utils.isArray([]));
+        djstest.assert(datajs.utils.isArray([1, 2]));
+        djstest.assert(!datajs.utils.isArray({}));
+        djstest.assert(!datajs.utils.isArray("1,2,3,4"));
+        djstest.assert(!datajs.utils.isArray());
+        djstest.assert(!datajs.utils.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.json.jsonParser(OData.json.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.json.jsonSerializer(OData.json.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/0367d2bc/datajs/tests/odata-links-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-links-functional-tests.html b/datajs/tests/odata-links-functional-tests.html
new file mode 100644
index 0000000..95b0d36
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-links-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-links-functional-tests.js b/datajs/tests/odata-links-functional-tests.js
new file mode 100644
index 0000000..6ae12ea
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-metadata-awareness-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-metadata-awareness-functional-tests.html b/datajs/tests/odata-metadata-awareness-functional-tests.html
new file mode 100644
index 0000000..75fb2f2
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-metadata-awareness-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-metadata-awareness-functional-tests.js b/datajs/tests/odata-metadata-awareness-functional-tests.js
new file mode 100644
index 0000000..b86b418
--- /dev/null
+++ b/datajs/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);


[02/13] [OLINGO-238] adopt odata-json-tests.js

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/0367d2bc/datajs/tests/odata-read-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-read-functional-tests.js b/datajs/tests/odata-read-functional-tests.js
new file mode 100644
index 0000000..ad2a9ea
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-request-functional-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-request-functional-tests.html b/datajs/tests/odata-request-functional-tests.html
new file mode 100644
index 0000000..4298493
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-request-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-request-functional-tests.js b/datajs/tests/odata-request-functional-tests.js
new file mode 100644
index 0000000..5304459
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-roundtrip-functional-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-roundtrip-functional-tests.js b/datajs/tests/odata-roundtrip-functional-tests.js
new file mode 100644
index 0000000..a8682f3
--- /dev/null
+++ b/datajs/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/0367d2bc/datajs/tests/odata-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-tests.js b/datajs/tests/odata-tests.js
new file mode 100644
index 0000000..c3724a6
--- /dev/null
+++ b/datajs/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);