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/04/07 15:37:06 UTC

[02/11] [OLINGO-238] Build infrastructure for datajs I

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/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..52c0c94
--- /dev/null
+++ b/datajs/tests/odata-atom-tests.js
@@ -0,0 +1,4756 @@
+/// <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/FoodStoreDataServiceV2.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 foodServiceV2FoodsSampleText = '' +
+    '<feed xml:base="http://localhost/tests/endpoints/FoodStoreDataServiceV2.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">Foods</title>' +
+    '  <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods</id>' +
+    '  <updated>2010-12-28T23:09:54Z</updated>' +
+    '  <link rel="self" title="Foods" href="Foods" />' +
+    '  <entry>' +
+    '    <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(0)</id>' +
+    '    <title type="text">flour</title>' +
+    '    <updated>2010-12-28T23:09:54Z</updated>' +
+    '    <author><name /></author>' +
+    '    <link rel="edit" title="Food" href="Foods(0)" />' +
+    '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+    '    <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+    '    <content type="application/xml">' +
+    '      <m:properties>' +
+    '        <d:FoodID m:type="Edm.Int32">0</d:FoodID>' +
+    '        <d:Name>flour</d:Name>' +
+    '        <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+    '        <d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+    '        <d:ProteinGrams m:type="Edm.Byte">3</d:ProteinGrams>' +
+    '        <d:FatGrams m:type="Edm.Int16">1</d:FatGrams>' +
+    '        <d:CarbohydrateGrams m:type="Edm.Int32">20</d:CarbohydrateGrams>' +
+    '        <d:CaloriesPerServing m:type="Edm.Int64">140</d:CaloriesPerServing>' +
+    '        <d:IsAvailable m:type="Edm.Boolean">true</d:IsAvailable>' +
+    '        <d:ExpirationDate m:type="Edm.DateTime">2010-12-25T12:00:00</d:ExpirationDate>' +
+    '        <d:ItemGUID m:type="Edm.Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+    '        <d:Weight m:type="Edm.Single">10</d:Weight>' +
+    '        <d:AvailableUnits m:type="Edm.SByte">1</d:AvailableUnits>' +
+    '        <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+    '          <d:Type m:null="true" />' +
+    '          <d:Color></d:Color>' +
+    '          <d:NumberPerPackage m:type="Edm.Int32">2147483647</d:NumberPerPackage>' +
+    '          <d:RequiresRefridgeration m:type="Edm.Boolean">false</d:RequiresRefridgeration>' +
+    '          <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+    '          <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+    '            <d:Length m:type="Edm.Decimal">79228162514264337593543950335</d:Length>' +
+    '            <d:Height m:type="Edm.Int16">32767</d:Height>' +
+    '            <d:Width m:type="Edm.Int64">9223372036854775807</d:Width>' +
+    '            <d:Volume m:type="Edm.Double">1.7976931348623157E+308</d:Volume>' +
+    '          </d:PackageDimensions>' +
+    '        </d:Packaging>' +
+    '      </m:properties>' +
+    '    </content>' +
+    '    <cooked:cooked cooked:length="2" cooked:height="1" cooked:width="3" xmlns:cooked="http://www.example.org/cooked/">' +
+    '      <cooked:volume>6</cooked:volume>' +
+    '    </cooked:cooked>' +
+    '    <pr:price pr:value="0.19999" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+    '  </entry>' +
+    '  <entry>' +
+    '    <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(1)</id>' +
+    '    <title type="text">sugar</title>' +
+    '    <updated>2010-12-28T23:09:54Z</updated>' +
+    '    <author>' +
+    '      <name />' +
+    '    </author>' +
+    '    <link rel="edit" title="Food" href="Foods(1)" />' +
+    '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(1)/Category" />' +
+    '    <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+    '    <content type="application/xml">' +
+    '      <m:properties>' +
+    '        <d:FoodID m:type="Edm.Int32">1</d:FoodID>' +
+    '        <d:Name>sugar</d:Name>' +
+    '        <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+    '        <d:MeasurementUnit>tsp</d:MeasurementUnit>' +
+    '        <d:ProteinGrams m:type="Edm.Byte">0</d:ProteinGrams>' +
+    '        <d:FatGrams m:type="Edm.Int16">0</d:FatGrams>' +
+    '        <d:CarbohydrateGrams m:type="Edm.Int32">4</d:CarbohydrateGrams>' +
+    '        <d:CaloriesPerServing m:type="Edm.Int64">16</d:CaloriesPerServing>' +
+    '        <d:IsAvailable m:type="Edm.Boolean">false</d:IsAvailable>' +
+    '        <d:ExpirationDate m:type="Edm.DateTime">2011-12-28T00:00:00</d:ExpirationDate>' +
+    '        <d:ItemGUID m:type="Edm.Guid">ffffffff-ffff-ffff-ffff-ffffffffffff</d:ItemGUID>' +
+    '        <d:Weight m:type="Edm.Single">0.1</d:Weight>' +
+    '        <d:AvailableUnits m:type="Edm.SByte">0</d:AvailableUnits>' +
+    '        <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+    '          <d:Type xml:space="preserve"> </d:Type>' +
+    '          <d:Color>BLUE</d:Color>' +
+    '          <d:NumberPerPackage m:type="Edm.Int32">-2147483648</d:NumberPerPackage>' +
+    '          <d:RequiresRefridgeration m:type="Edm.Boolean">true</d:RequiresRefridgeration>' +
+    '          <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+    '          <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+    '            <d:Length m:type="Edm.Decimal">-79228162514264337593543950335</d:Length>' +
+    '            <d:Height m:type="Edm.Int16">-32768</d:Height>' +
+    '            <d:Width m:type="Edm.Int64">-9223372036854775808</d:Width>' +
+    '            <d:Volume m:type="Edm.Double">-1.7976931348623157E+308</d:Volume>' +
+    '          </d:PackageDimensions>' +
+    '        </d:Packaging>' +
+    '        <d:CookedSize m:type="DataJS.Tests.V2.CookedDimensions" m:null="true" />' +
+    '      </m:properties>' +
+    '    </content>' +
+    '    <cooked:cooked cooked:length="" cooked:height="" cooked:width="" xmlns:cooked="http://www.example.org/cooked/">' +
+    '      <cooked:volume></cooked:volume>' +
+    '    </cooked:cooked>' +
+    '    <pr:price pr:value="0.2" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+    '  </entry>' +
+    '</feed>';
+
+    var foodServiceV2MetadataText = '' +
+    '<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">' +
+    '  <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0">' +
+    '    <Schema Namespace="DataJS.Tests.V2" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns="http://schemas.microsoft.com/ado/2007/05/edm">' +
+    '      <EntityType Name="Category">' +
+    '        <Key>' +
+    '          <PropertyRef Name="CategoryID" />' +
+    '        </Key>' +
+    '        <Property Name="CategoryID" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="true" />' +
+    '        <NavigationProperty Name="Foods" Relationship="DataJS.Tests.V2.Category_Foods" FromRole="Category" ToRole="Foods" />' +
+    '      </EntityType>' +
+    '      <EntityType Name="PreparedFood" BaseType="DataJS.Tests.V2.Food">' +
+    '        <Property Name="Instructions" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="NumberOfIngredients" Type="Edm.Single" Nullable="false" />' +
+    '      </EntityType>' +
+    '      <EntityType Name="Food">' +
+    '        <Key>' +
+    '          <PropertyRef Name="FoodID" />' +
+    '        </Key>' +
+    '        <Property Name="FoodID" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="true" />' +
+    '        <Property Name="UnitPrice" Type="Edm.Double" Nullable="false" m:FC_TargetPath="price/@value" m:FC_NsUri="http://www.example.org/price/" m:FC_NsPrefix="pr" m:FC_KeepInContent="false" />' +
+    '        <Property Name="ServingSize" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="MeasurementUnit" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="ProteinGrams" Type="Edm.Byte" Nullable="false" />' +
+    '        <Property Name="FatGrams" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="CarbohydrateGrams" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="CaloriesPerServing" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="IsAvailable" Type="Edm.Boolean" Nullable="false" />' +
+    '        <Property Name="ExpirationDate" Type="Edm.DateTime" Nullable="false" />' +
+    '        <Property Name="ItemGUID" Type="Edm.Guid" Nullable="false" />' +
+    '        <Property Name="Weight" Type="Edm.Single" Nullable="false" />' +
+    '        <Property Name="AvailableUnits" Type="Edm.SByte" Nullable="false" />' +
+    '        <Property Name="Packaging" Type="DataJS.Tests.V2.Package" Nullable="false" />' +
+    '        <Property Name="CookedSize" Type="DataJS.Tests.V2.CookedDimensions" Nullable="false" m:FC_TargetPath="cooked/volume" m:FC_NsUri="http://www.example.org/cooked/" m:FC_NsPrefix="cooked" m:FC_SourcePath="Volume" m:FC_KeepInContent="false" m:FC_TargetPath_1="cooked/@width" m:FC_NsUri_1="http://www.example.org/cooked/" m:FC_NsPrefix_1="cooked" m:FC_SourcePath_1="Width" m:FC_KeepInContent_1="false" m:FC_TargetPath_2="cooked/@height" m:FC_NsUri_2="http://www.example.org/cooked/" m:FC_NsPrefix_2="cooked" m:FC_SourcePath_2="Height" m:FC_KeepInContent_2="false" m:FC_TargetPath_3="cooked/@length" m:FC_NsUri_3="http://www.example.org/cooked/" m:FC_NsPrefix_3="cooked" m:FC_SourcePath_3="Length" m:FC_KeepInContent_3="false" />' +
+    '        <NavigationProperty Name="Category" Relationship="DataJS.Tests.V2.Food_Category" FromRole="Food" ToRole="Category" />' +
+    '      </EntityType>' +
+    '      <ComplexType Name="Package">' +
+    '        <Property Name="Type" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="Color" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="NumberPerPackage" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="RequiresRefridgeration" Type="Edm.Boolean" Nullable="false" />' +
+    '        <Property Name="ShipDate" Type="Edm.DateTime" Nullable="false" />' +
+    '        <Property Name="PackageDimensions" Type="DataJS.Tests.V2.Dimensions" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <ComplexType Name="Dimensions">' +
+    '        <Property Name="Length" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="Height" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="Width" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="Volume" Type="Edm.Double" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <ComplexType Name="CookedDimensions">' +
+    '        <Property Name="Length" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="Height" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="Width" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="Volume" Type="Edm.Double" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <Association Name="Category_Foods">' +
+    '        <End Role="Category" Type="DataJS.Tests.V2.Category" Multiplicity="*" />' +
+    '        <End Role="Foods" Type="DataJS.Tests.V2.Food" Multiplicity="*" />' +
+    '      </Association>' +
+    '      <Association Name="Food_Category">' +
+    '        <End Role="Food" Type="DataJS.Tests.V2.Food" Multiplicity="*" />' +
+    '        <End Role="Category" Type="DataJS.Tests.V2.Category" Multiplicity="0..1" />' +
+    '      </Association>' +
+    '      <EntityContainer Name="FoodContainer" m:IsDefaultEntityContainer="true">' +
+    '        <EntitySet Name="Categories" EntityType="DataJS.Tests.V2.Category" />' +
+    '        <EntitySet Name="Foods" EntityType="DataJS.Tests.V2.Food" />' +
+    '        <AssociationSet Name="Category_Foods" Association="DataJS.Tests.Category_Foods">' +
+    '          <End Role="Category" EntitySet="Categories" />' +
+    '          <End Role="Foods" EntitySet="Foods" />' +
+    '        </AssociationSet>' +
+    '        <AssociationSet Name="Food_Category" Association="DataJS.Tests.V2.Food_Category">' +
+    '          <End Role="Food" EntitySet="Foods" />' +
+    '          <End Role="Category" EntitySet="Categories" />' +
+    '        </AssociationSet>' +
+    '        <FunctionImport Name="ResetData" m:HttpMethod="POST" />' +
+    '        <FunctionImport Name="FoodsAvailable" ReturnType="Collection(Edm.String)" m:HttpMethod="GET" />' +
+    '        <FunctionImport Name="PackagingTypes" ReturnType="Collection(DataJS.Tests.V2.Package)" m:HttpMethod="GET" />' +
+    '      </EntityContainer>' +
+    '    </Schema>' +
+    '  </edmx:DataServices>' +
+    '</edmx:Edmx>';
+
+    djstest.addTest(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.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: foodServiceV2FoodsSampleText,
+            headers: { "Accept": "application/json", "unknown": "u", "content-type": "application/atom+xml", "dataserviceversion": "2.0" }
+        });
+
+        OData.read("/foo", function (data, response) {
+            djstest.assertAreEqual(data.results.length, 2, "data.results.length has two entries");
+            djstest.assertAreEqual(response.headers.Accept, "application/json", "Accept available");
+            djstest.assertAreEqual(response.headers.unknown, "u", "u unmodified");
+            djstest.assertAreEqual(response.headers["Content-Type"], "application/atom+xml", "Content-Type available");
+            djstest.assertAreEqual(response.headers["DataServiceVersion"], "2.0", "DataServiceVersion 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["Content-Type"], "application/atom+xml", "json found");
+            djstest.assert(request.body.indexOf(":bar") !== -1, "format recognized and applied as XML");
+            djstest.done();
+        });
+
+        var request = {
+            method: "POST",
+            requestUri: "/foo",
+            data: { "bar": 123 },
+            headers: { "content-type": "application/atom+xml" }
+        };
+        OData.request(request, function (data) {
+        }, undefined, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(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.addTest(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.addTest(function parseCustomizationSampleTest() {
+        var payload = foodServiceV2FoodsSampleText;
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        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.V2.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.PackageV2", "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.addTest(function parseIntoPropertiesTest() {
+        var payload = '' +
+            '<entry xml:base="http://localhost/tests/endpoints/FoodStoreDataServiceV2.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">' +
+            '  <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(0)</id>' +
+            '  <title type="text">flour</title>' +
+            '  <updated>2010-12-28T23:09:54Z</updated>' +
+            '  <author><name /></author>' +
+            '  <link rel="edit" title="Food" href="Foods(0)" />' +
+            '  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+            '  <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+            '  <content type="application/xml">' +
+            '    <m:properties>' +
+            '      <d:FoodID m:type="Edm.Int32">0</d:FoodID>' +
+            '      <d:Name>flour</d:Name>' +
+            '      <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+            '      <d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+            '      <d:ProteinGrams m:type="Edm.Byte">3</d:ProteinGrams>' +
+            '      <d:FatGrams m:type="Edm.Int16">1</d:FatGrams>' +
+            '      <d:CarbohydrateGrams m:type="Edm.Int32">20</d:CarbohydrateGrams>' +
+            '      <d:CaloriesPerServing m:type="Edm.Int64">140</d:CaloriesPerServing>' +
+            '      <d:IsAvailable m:type="Edm.Boolean">true</d:IsAvailable>' +
+            '      <d:ExpirationDate m:type="Edm.DateTime">2010-12-25T12:00:00</d:ExpirationDate>' +
+            '      <d:ItemGUID m:type="Edm.Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+            '      <d:Weight m:type="Edm.Single">10</d:Weight>' +
+            '      <d:AvailableUnits m:type="Edm.SByte">1</d:AvailableUnits>' +
+            '      <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+            '        <d:Type m:null="true" />' +
+            '        <d:Color></d:Color>' +
+            '        <d:NumberPerPackage m:type="Edm.Int32">2147483647</d:NumberPerPackage>' +
+            '        <d:RequiresRefridgeration m:type="Edm.Boolean">false</d:RequiresRefridgeration>' +
+            '        <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+            '        <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+            '          <d:Length m:type="Edm.Decimal">79228162514264337593543950335</d:Length>' +
+            '          <d:Height m:type="Edm.Int16">32767</d:Height>' +
+            '          <d:Width m:type="Edm.Int64">9223372036854775807</d:Width>' +
+            '          <d:Volume m:type="Edm.Double">1.7976931348623157E+308</d:Volume>' +
+            '        </d:PackageDimensions>' +
+            '      </d:Packaging>' +
+            '    </m:properties>' +
+            '  </content>' +
+            '  <cooked:cooked cooked:length="2" cooked:height="1" cooked:width="3" xmlns:cooked="http://www.example.org/cooked/">' +
+            '    <cooked:volume>6</cooked:volume>' +
+            '  </cooked:cooked>' +
+            '  <pr:price pr:value="0.19999" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+            '</entry>';
+
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        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.V2.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.addTest(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/tests/endpoints/FoodStoreDataService.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">' +
+        '  <entry>' +
+        '    <id>http://localhost/tests/endpoints/FoodStoreDataService.svc/Foods(2)</id>' +
+        '    <link rel="edit" title="Food" href="Foods(2)" />' +
+        '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(2)/Category">' +
+        '      <m:inline />' +
+        '    </link>' +
+        '    <category term="DataJS.Tests.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+        '    <content type="application/xml">' +
+        '      <m:properties>' +
+        '        <d:FoodID m:type="Edm.Int32">2</d:FoodID>' +
+        '        <d:Name>1 Chicken Egg</d:Name>' +
+        '        <d:UnitPrice m:type="Edm.Double">0.55</d:UnitPrice>' +
+        '      </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.addTest(function serializeEpmTest() {
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        var data = { __metadata: { type: "DataJS.Tests.V2.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.addTest(function writeNullComplexTypeTest() {
+
+        // Verify that the server can be updated to set a complex value to null.
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        resetFoodData();
+
+        // Without metadata, this will fail because the server version won't be set to 2.0.
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        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.addTest(function writeNonNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        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.addTest(function writeNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(function atomReadFeedV1Test() {
+        var simpleFeedString = "\
+        <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=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <entry> \r\n\
+             <id>entry id</id> \r\n\
+             <title>empty entry</title> \r\n\
+           </entry> \r\n\
+        </feed> \r\n";
+
+        var expectedFeed = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: []
+            },
+            results: [
+                { __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: []
+                }
+                }
+           ]
+        };
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(simpleFeedString).documentElement);
+
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a V1 payload");
+        djstest.assertAreEqualDeep(feed, expectedFeed, "atomReadFeed didn't return the expected feed");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedV2Test() {
+        var simpleFeedString = "\
+        <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=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <m:count>2</m:count> \r\n\
+           <entry> \r\n\
+             <id>entry id</id> \r\n\
+             <title>empty entry</title> \r\n\
+           </entry> \r\n\
+           <link rel=\'next\' href=\'http://nexturi\'/> \r\n\
+        </feed> \r\n";
+
+        var expectedFeed = {
+            __count: 2,
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: [],
+                next_extensions: []
+            },
+            __next: "http://nexturi",
+            results: [
+                { __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: []
+                }
+                }
+           ]
+        };
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(simpleFeedString).documentElement);
+
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a V2 payload");
+        djstest.assertAreEqualDeep(feed, expectedFeed, "atomReadFeed didn't return the expected feed");
+        djstest.done();
+    });
+
+    djstest.addTest(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", "DataServiceVersion": "3.0" }, body: feed };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, expected, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+    });
+
+    djstest.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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.addTest(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>                               

<TRUNCATED>