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 2015/02/23 11:11:16 UTC

[09/41] olingo-odata4-js git commit: [OLINGO-541] Move the odatajs branch one directory up

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/endpoints/FoodStoreDataServiceV4.svc
----------------------------------------------------------------------
diff --git a/tests/endpoints/FoodStoreDataServiceV4.svc b/tests/endpoints/FoodStoreDataServiceV4.svc
new file mode 100644
index 0000000..ac1cfe7
--- /dev/null
+++ b/tests/endpoints/FoodStoreDataServiceV4.svc
@@ -0,0 +1,590 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+
+<%@ ServiceHost Language="C#" Factory="Microsoft.OData.Service.DataServiceHostFactory, Microsoft.OData.Service, Version=6.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.V4.FoodStoreDataService" %>
+
+namespace DataJS.Tests.V4
+{
+    using System;
+    using System.Collections.Generic;
+    using Microsoft.OData.Service;
+    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>
+    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>
+    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/503b4417/tests/endpoints/LargeCollectionService.svc
----------------------------------------------------------------------
diff --git a/tests/endpoints/LargeCollectionService.svc b/tests/endpoints/LargeCollectionService.svc
new file mode 100644
index 0000000..bfbd3ef
--- /dev/null
+++ b/tests/endpoints/LargeCollectionService.svc
@@ -0,0 +1,113 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+
+<%@ ServiceHost Language="C#" Factory="Microsoft.OData.Service.DataServiceHostFactory, Microsoft.OData.Service, Version=6.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.LargeCollectionService" %>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using Microsoft.OData.Service;
+    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/503b4417/tests/endpoints/web.config
----------------------------------------------------------------------
diff --git a/tests/endpoints/web.config b/tests/endpoints/web.config
new file mode 100644
index 0000000..116a567
--- /dev/null
+++ b/tests/endpoints/web.config
@@ -0,0 +1,46 @@
+<?xml version='1.0'?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<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.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+        <add assembly="Microsoft.OData.Service, Version=6.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+        <add assembly="Microsoft.OData.Client, Version=6.5.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>

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/node-test-setup.js
----------------------------------------------------------------------
diff --git a/tests/node-test-setup.js b/tests/node-test-setup.js
new file mode 100644
index 0000000..838bb36
--- /dev/null
+++ b/tests/node-test-setup.js
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+//Creates the global objects
+
+//tools
+djstest = require("./common/djstest.js");
+MockHttpClient = require("./common/mockHttpClient.js").init({});
+
+//lib
+datajs = require('././lib/datajs.js');
+OData = require('./../src/lib/odata.js');
+
+
+

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/odata-batch-functional-tests.html
----------------------------------------------------------------------
diff --git a/tests/odata-batch-functional-tests.html b/tests/odata-batch-functional-tests.html
new file mode 100644
index 0000000..baf9b54
--- /dev/null
+++ b/tests/odata-batch-functional-tests.html
@@ -0,0 +1,49 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ -->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>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/odataVerifyReader.js"></script>
+    <script type="text/javascript" src="common/TestSynchronizerClient.js"></script>
+    <script type="text/javascript">
+        window.TestSynchronizer.init(QUnit);
+    </script>
+
+    <script type="text/javascript" src="../build/odatajs-4.0.0-beta-01.js"></script>  
+    <script type="text/javascript" src="common/common.js"></script> 
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="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/503b4417/tests/odata-batch-functional-tests.js
----------------------------------------------------------------------
diff --git a/tests/odata-batch-functional-tests.js b/tests/odata-batch-functional-tests.js
new file mode 100644
index 0000000..4ff56ce
--- /dev/null
+++ b/tests/odata-batch-functional-tests.js
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function (window, undefined) {
+    OData.defaultHandler.accept = "application/json;q=0.9, */*;q=0.1";
+
+    var 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) {
+        odatajs.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.batch.batchHandler);
+    };
+
+    var verifyBatchResponses = function (batchRequests, elementTypes, serviceRoot, batchResponses, done) {
+        forEachAsync(batchRequests, function (index, batchRequest, doneOne) {
+            if (batchRequest.requestUri) {
+                var readFeedOrEntry = elementTypes[index] == "feed" ? ODataVerifyReader.readFeed : ODataVerifyReader.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);
+                ODataVerifyReader.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 cloneHeadersWithContentId = function (mimeHeaders, count) {
+        var headers = djstest.clone(mimeHeaders);
+        headers["Content-ID"] = count;
+        return headers;
+    };
+
+    var service = "./endpoints/FoodStoreDataServiceV4.svc";
+    var batchUri = service + "/$batch";
+
+    var httpStatusCode = {
+        ok: 200,
+        created: 201,
+        accepted: 202,
+        noContent: 204
+    };
+
+    var mimeTypes = [undefined, "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: cloneHeadersWithContentId(params.mimeHeaders, 1), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            }
+                        ]
+                    },
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories(1)", method: "PUT", headers: cloneHeadersWithContentId(params.mimeHeaders, 2), data:
+                                { CategoryID: 1, Name: "Updated Category" }
+                            },
+                            { requestUri: "Categories(0)", method: "DELETE", headers: cloneHeadersWithContentId(params.mimeHeaders, 3) }
+                        ]
+                    }
+                ];
+            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: cloneHeadersWithContentId(params.mimeHeaders, 1), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            }
+                        ]
+                    },
+                    { requestUri: "Foods?$top=2", method: "GET", headers: djstest.clone(params.acceptHeaders) },
+                    {
+                        __changeRequests: [
+                            { requestUri: "Categories(1)", method: "PUT", headers: cloneHeadersWithContentId(params.mimeHeaders, 2), data:
+                                { CategoryID: 1, Name: "Updated Category" }
+                            },
+                            { requestUri: "Categories(0)", method: "DELETE", headers: cloneHeadersWithContentId(params.mimeHeaders, 3) }
+                        ]
+                    }
+                ];
+            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: cloneHeadersWithContentId(params.mimeHeaders, 1), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            },
+                            { requestUri: "Categories(1)", method: "PUT", headers: cloneHeadersWithContentId(params.mimeHeaders, 2), 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: cloneHeadersWithContentId(params.mimeHeaders, 1), data:
+                                { CategoryID: 42, Name: "New Category" }
+                            },
+                            { requestUri: "Categories(1)", method: "PUT", headers: cloneHeadersWithContentId(params.mimeHeaders, 2), 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 retrieveInsideChangeset(params) {
+
+            var batchRequests = [
+                    { requestUri: "Foods(0)", method: "GET" },
+                    { __changeRequests: [
+                            { requestUri: "Categories", method: "POST", headers: cloneHeadersWithContentId(params.mimeHeaders, 1), data: { CategoryID: 42, Name: "New Category"} },
+                            { requestUri: "Categories(1)", method: "PUT", headers: cloneHeadersWithContentId(params.mimeHeaders, 2), data: { CategoryID: 1, Name: "Updated Category"} }
+                        ]
+                    },
+                    { requestUri: "Foods(1)", method: "GET" },
+                    { __changeRequests: [
+                            { requestUri: "Categories", method: "POST", headers: cloneHeadersWithContentId(params.mimeHeaders, 3), data: { CategoryID: 42, Name: "New Category"} },
+                            { requestUri: "Categories(1)", method: "PUT", headers: cloneHeadersWithContentId(params.mimeHeaders, 4), data: { CategoryID: 1, Name: "Updated Category"} },
+                            { requestUri: "Foods", method: "GET" }
+                        ]
+                    }
+                ];
+
+            odatajs.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.batch.batchHandler);
+        }, "Retrieve inside changeset: mimeType = " + mimeType, {mimeHeaders: mimeHeaders });
+    });
+
+    /*On Odata V4 spec, the POST/PUT/Delete operations are allowed outside of changesets.*/
+    djstest.addTest(function updateOutsideChangeset() {
+        var batchRequests = [{ requestUri: "Categories", method: "POST", data: { CategoryID: 42, Name: "New Category"}}];
+
+        djstest.assertsExpected(1);
+        odatajs.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 that there is no error occurred.");
+                djstest.done();
+            }, unexpectedErrorHandler, OData.batch.batchHandler
+        );
+    }, "Update outside changeset");
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/odata-batch-tests.js
----------------------------------------------------------------------
diff --git a/tests/odata-batch-tests.js b/tests/odata-batch-tests.js
new file mode 100644
index 0000000..71a1547
--- /dev/null
+++ b/tests/odata-batch-tests.js
@@ -0,0 +1,559 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// odata-batch-tests.js
+
+(function (window, undefined) {
+    // DATAJS INTERNAL START
+    var defaultAcceptString = "application/json;q=0.9, */*;q=0.1";
+
+    var testPayload = {
+        CategoryID : 42,
+        Name: "New Category",
+        ID : "odata",
+        version: "4.0"
+    };
+    
+    var jsonPayload = window.odatajs.oData.json.jsonSerializer(window.odatajs.oData.json.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 = window.odatajs.oData.batch.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 = window.odatajs.oData.handler.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();
+        });
+
+        odatajs.oData.request(request, null, null, window.odatajs.oData.batch.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 }
+                        ]
+                },
+                { 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 = window.odatajs.oData.handler.contentType(request.body.substring(start, end)).properties["boundary"];
+            var batchBoundary = window.odatajs.oData.handler.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();
+        });
+
+        odatajs.oData.request(request, null, null, window.odatajs.oData.batch.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 }
+                        ]
+                    }
+            ]
+            }
+        };
+
+        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 = window.odatajs.oData.handler.contentType(request.body.substring(start, end)).properties["boundary"];
+            var batchBoundary = window.odatajs.oData.handler.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();
+        });
+
+        odatajs.oData.request(request, null, null, window.odatajs.oData.batch.batchHandler, MockHttpClient);
+    });
+
+    djstest.addTest(function serializeNestedChangeSetsTest() {
+        var request = {
+            requestUri: "http://temp.org",
+            method: "POST",
+            data: testPayload
+        };
+
+        djstest.expectException(function () {
+            odatajs.oData.request(request, null, null, window.odatajs.oData.batch.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 () {
+            odatajs.oData.request(request, null, null, window.odatajs.oData.batch.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:4002/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(42)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:4002/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:4002/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(43)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:4002/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);
+        odatajs.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:4002/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(42)", "part 1 of the response was read");
+            djstest.assertAreEqual(data.__batchResponses[1].headers["Location"], "http://localhost:4002/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, window.odatajs.oData.batch.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:4002/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(42)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:4002/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:4002/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(41)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:4002/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:4002/tests/endpoints/FoodStoreDataServiceV4.svc/Categories(43)\r\n\
+\r\n\
+{\"@odata.context\":\"http://localhost:4002/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);
+        odatajs.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:4002/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:4002/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:4002/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, window.odatajs.oData.batch.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:4002/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);
+        odatajs.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:4002/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, window.odatajs.oData.batch.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:4002/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);
+        odatajs.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:4002/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, window.odatajs.oData.batch.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 = window.odatajs.oData.batch.batchHandler.partHandler;
+
+        window.odatajs.oData.batch.batchHandler.partHandler = testHandler;
+
+        window.odatajs.oData.batch.batchHandler.write(request, { recognizeDates: true });
+        window.odatajs.oData.batch.batchHandler.read(response, { recognizeDates: true });
+
+        window.odatajs.oData.batch.batchHandler.partHandler = oldPartHandler;
+
+        djstest.done();
+    });
+
+
+    // DATAJS INTERNAL END
+})(this);

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/odata-cache-filter-functional-tests.html
----------------------------------------------------------------------
diff --git a/tests/odata-cache-filter-functional-tests.html b/tests/odata-cache-filter-functional-tests.html
new file mode 100644
index 0000000..35fbd08
--- /dev/null
+++ b/tests/odata-cache-filter-functional-tests.html
@@ -0,0 +1,52 @@
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ -->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <title>datajs.cache  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="../build/odatajs-4.0.0-beta-01.js"></script>
+    <script type="text/javascript" src="common/common.js"></script>
+    <script type="text/javascript" src="common/djstest.js"></script>
+    <script type="text/javascript" src="common/CacheVerifier.js"></script>
+    <script type="text/javascript" src="common/ObservableHttpClient.js"></script>
+    <script type="text/javascript" src="common/odataVerifyReader.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