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

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

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/common.js
----------------------------------------------------------------------
diff --git a/tests/common/common.js b/tests/common/common.js
new file mode 100644
index 0000000..8151fc1
--- /dev/null
+++ b/tests/common/common.js
@@ -0,0 +1,25 @@
+/*
+ * 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) {
+        window.temp = window.odatajs;
+        window.temp.store = window.odatajs.store;
+        window.temp.cache = window.odatajs.cache;
+        window.odatajs = window.temp;
+        delete window.temp;
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/djstest-browser.js
----------------------------------------------------------------------
diff --git a/tests/common/djstest-browser.js b/tests/common/djstest-browser.js
new file mode 100644
index 0000000..c37f7e9
--- /dev/null
+++ b/tests/common/djstest-browser.js
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ */
+
+
+// Because this code contains a init function to be useable directly inside 
the browser as well as in nodejs
+// we define the @namespace djstest here instead of the a @module name djstest
+
+/** Create namespace djstest in window.djstest when this file is loaded as 
JavaScript by the browser
+ * @namespace djstest
+ */
+
+
+var init = function init () {
+
+    var localDjstest = {};
+
+    // Initialize indexedDB if the window object is available
+    localDjstest.indexedDB = window.mozIndexedDB || window.webkitIndexedDB || 
window.msIndexedDB || window.indexedDB;
+
+    /** Cleans all the test data saved in the IndexedDb database.
+     * @param {Array} storeNames - Array of store objects with a property that 
is the name of the store
+     * @param {Function} done - Callback function
+     */
+    localDjstest.cleanStoreOnIndexedDb = function (storeObjects, done) {
+        var IDBTransaction = window.IDBTransaction || 
window.webkitIDBTransaction || {};
+
+        function deleteObjectStores(db) {
+            for ( var i = 0 ; i < db.objectStoreNames.length ; i ++) {
+                db.deleteObjectStore(db.objectStoreNames[i]);
+            }
+        }
+        var job;
+
+        if (localDjstest.indexedDB) {
+            job = new djstest.Job();
+            for ( var i = 0 ; i < storeObjects.length ; i ++) {
+                storeObject = storeObjects[i];
+                job.queue((function (storeObject) {
+                    return function (success, fail) {
+                        var dbname = "_datajs_" + storeObject.name;
+                        var request = localDjstest.indexedDB.open(dbname);
+                        request.onsuccess = function (event) {
+                            var db = request.result;
+
+                            if ("setVersion" in db) {
+                                var versionRequest = db.setVersion("0.1");
+                                versionRequest.onsuccess = function (event) {
+                                    var transaction = 
versionRequest.transaction;
+                                    transaction.oncomplete = function () {
+                                        db.close();
+                                        success();
+                                    };
+                                    deleteObjectStores(db);
+                                };
+                                versionRequest.onerror = function (e) {
+                                    djstest.fail("Error on cleanup - code: " + 
e.code + " name: " + e.name + "message: " + message);
+                                    fail();
+                                };
+                                return;
+                            }
+
+                            // new api cleanup
+                            db.close();
+                            var deleteRequest = 
localDjstest.indexedDB.deleteDatabase(dbname);
+                            deleteRequest.onsuccess = function (event) {
+                                djstest.log("djstest indexeddb cleanup - 
deleted database " + dbname);
+                                success();
+                            };
+                            deleteRequest.onerror = function (e) {
+                                djstest.fail("djstest indexeddb cleanup - 
error deleting database " + dbname);
+                                fail();
+                            };
+                            djstest.log("djstest indexeddb cleanup - requested 
deletion of database " + dbname);
+                        };
+
+                        request.onerror = function (e) {
+                            djstest.fail(e.code + ": " + e.message);
+                        };
+                    };
+                })(storeObject));
+            }
+        }
+
+        if (job) {
+            job.run(function (succeeded) {
+                if (!succeeded) {
+                    djstest.fail("cleanup job failed");
+                }
+                done();
+            });
+        }
+        else {
+            done();
+        }
+    };
+
+
+    // Disable caching to ensure that every test-related AJAX request is 
actually being sent,
+    // and set up a default error handler
+    if (typeof window !== undefined) {
+        $.ajaxSetup({
+            cache: false,
+            error: function (jqXHR, textStatus, errorThrown) {
+                // Work around bug in IE-Mobile on Windows Phone 7
+                if (jqXHR.status !== 1223) {
+                    var err = {
+                        status: jqXHR.status,
+                        statusText: jqXHR.statusText,
+                        responseText: jqXHR.responseText
+                    };
+                    djstest.fail("AJAX request failed with: " + 
djstest.toString(err));
+                }
+                djstest.done();
+            }
+        });
+    }
+    return localDjstest;
+};
+
+//export djstest
+
+if (typeof window !== 'undefined') {
+    //expose to browsers window object
+    if ( window.djstest === undefined) {
+        window.djstest = init();
+    } else {
+        var tmp = init();
+        $.extend( window.djstest,tmp);
+    }
+} else {
+    //expose in commonjs style
+    module.exports = init();
+}
+

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/djstest.js
----------------------------------------------------------------------
diff --git a/tests/common/djstest.js b/tests/common/djstest.js
new file mode 100644
index 0000000..5268467
--- /dev/null
+++ b/tests/common/djstest.js
@@ -0,0 +1,419 @@
+/*
+ * 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.
+ */
+
+
+// Because this code contains a init function to be useable directly inside 
the browser as well as in nodejs
+// we define the @namespace djstest here instead of the a @module name djstest
+
+/** Create namespace djstest in window.djstest when this file is loaded as 
JavaScript by the browser
+ * @namespace djstest
+ */
+
+
+var init = function init () {
+    var djstest = {};
+  
+
+    /** Constructs a Job object that allows for enqueuing and synchronizing 
the execution of functions.
+     * @class Job
+     * @constructor
+     * @returns {Object} Job object
+     */
+    djstest.Job = function () {
+        
+        var currentTask = -1;
+        var tasks = [];
+
+        var failedTasks = 0;
+
+        /** Adds a function to the job queue regardless if the queue is 
already executing or not.
+         * @method djstest.Job#queue
+         * @param {Function} fn - Function to execute.
+         */
+        this.queue = function (fn) {
+            
+            tasks.push(fn);
+        };
+
+        /** Adds a function to the front of the job queue regardless if the 
queue is already executing or not.
+         * @method djstest.Job#queueNext
+         * @param {Function} fn - Function to execute.
+         */
+        this.queueNext = function (fn) {
+        
+            if (currentTask < 0) {
+                tasks.unshift(fn);
+            } else {
+                tasks.splice(currentTask + 1, 0, fn);
+            }
+        };
+
+        /** Starts the execution of this job.
+         * @method djstest.Job#run
+         * @param {Function} done - Callback invoked when the job has finished 
executing all of its enqueued tasks.
+         */
+        this.run = function (done) {
+            /// This method does nothing if called on a unit of work that is 
already executing.
+            if (currentTask >= 0) {
+                return;
+            }
+
+            if (tasks.length === 0) {
+                done(true);
+                return;
+            }
+
+            /**
+             * @method djstest.Job~makeTaskDoneCallBack
+            */
+            function makeTaskDoneCallBack(failed) {
+                return function () {
+                    // Track the failed task and continue the execution of the 
job. 
+                    if (failed) {
+                        failedTasks++;
+                    }
+                    currentTask++;
+                    if (currentTask === tasks.length) {
+                        done(failedTasks === 0);
+                    } else {
+                        runNextTask();
+                    }
+                };
+            }
+
+            /** Executes the next function in the queue.
+             * @method djstest.Job~runNextTask
+            */
+            function runNextTask() {
+                defer(function () {
+                    try {
+                        tasks[currentTask](makeTaskDoneCallBack(false), 
makeTaskDoneCallBack(true));
+                    } catch (e) {
+                        makeTaskDoneCallBack(true)();
+                    }
+                });
+            }
+
+            currentTask = 0;
+            runNextTask();
+        };
+    };
+
+    /** Defers the execution of an arbitrary function that takes no parameters.
+     * @memberof djstest
+     * @inner
+     * @param {Function} fn - Function to schedule for later execution.
+     */
+    function defer(fn) {
+        setTimeout(fn, 0);
+    }
+
+    /** Exposes date values for Date objects to facilitate debugging
+     * @memberof djstest
+     * @inner
+     * @param {Object} data - The object to operate on
+     */
+    function exposeDateValues(data) {
+     
+        if (typeof data === "object") {
+            if (data instanceof Date) {
+                data.__date__ = data.toUTCString();
+            }
+            else {
+                for (var prop in data) {
+                    exposeDateValues(data[prop]);
+                }
+            }
+        }
+
+        return data;
+    }
+
+    /** Determines the name of a function.
+     * @memberof djstest
+     * @inner
+     * @param {String} text - Function text.
+     * @returns {String} The name of the function from text if found; the 
original text otherwise.
+     */
+    function extractFunctionName(text) {
+
+        var index = text.indexOf("function ");
+        if (index < 0) {
+            return text;
+        }
+
+        var nameStart = index + "function ".length;
+        var parensIndex = text.indexOf("(", nameStart);
+        if (parensIndex < 0) {
+            return text;
+        }
+
+        var result = text.substr(nameStart, parensIndex - nameStart);
+        if (result.indexOf("test") === 0) {
+            result = result.substr("test".length);
+        }
+
+        return result;
+    }
+
+    /** Removes metadata annotations from the specified object.
+     * @memberof djstest
+     * @inner
+     * @param data - Object to remove metadata from; possibly null.
+     */
+    function removeMetadata(data) {
+
+        if (typeof data === "object" && data !== null) {
+            delete data.__metadata;
+            for (var prop in data) {
+                removeMetadata(data[prop]);
+            }
+        }
+    }
+
+    /** Add the unit test cases
+     * @param disable - Indicate whether this test case should be disabled
+    */
+    djstest.addFullTest = function (disable, fn, name, arg, timeout) {
+
+        if (disable !== true) {
+            djstest.addTest(fn, name, arg, timeout);
+        }
+    };
+
+    /** Add the unit test cases
+     * @param disable - Indicate whether this test case should be disabled
+    */
+    djstest.addTest = function (fn, name, arg, timeout) {
+        if (!name) {
+            name = extractFunctionName(fn.toString());
+        }
+
+        test(name, function () {
+            if (!timeout) {
+                timeout = 20000;
+            }
+
+            QUnit.config.testTimeout = timeout;
+            QUnit.stop();
+            fn.call(this, arg);
+        });
+    };
+
+    /** Asserts that a condition is true.
+     * @param {Boolean} test - Condition to test.
+     * @param {String} message - Text message for condition being tested.
+     */
+    djstest.assert = function (test, message) {
+        
+        QUnit.ok(test, message);
+    };
+
+    /** Asserts that the values of the expected and actualobjects are equal.
+     * @memberof djstest
+     * @inner
+     */
+    djstest.assertAreEqual = function (actual, expected, message) {
+        QUnit.equal(actual, expected, message);
+    };
+
+    /** Asserts that the actual and expected objects are the same.
+     */
+    djstest.assertAreEqualDeep = function (actual, expected, message) {
+        QUnit.deepEqual(exposeDateValues(actual), exposeDateValues(expected), 
message);
+    };
+
+    /** Asserts that the actual and expected objects are the same but removes 
the metadata bevore
+     */
+    djstest.assertWithoutMetadata = function (actual, expected, message) {
+        removeMetadata(actual);
+        removeMetadata(expected);
+        djstest.assertAreEqualDeep(actual, expected, message);
+    };
+
+    /** Calls each async action in asyncActions, passing each action a 
function which keeps a count and
+     * calls the passed done function when all async actions complete
+     * @param {Array} asyncActions -Array of asynchronous actions to be 
executed, 
+     * each taking a single parameter - the callback function to call when the 
action is done.</param>
+     * @param {Function} done - Function to be executed in the last async 
action to complete.
+     */
+    djstest.asyncDo = function (asyncActions, done) {
+
+        var count = 0;
+        var doneOne = function () {
+            count++;
+            if (count >= asyncActions.length) {
+                done();
+            }
+        };
+
+        if (asyncActions.length > 0) {
+            for ( var i = 0; i < asyncActions.length; i++) {
+                asyncActions[i](doneOne);
+            }
+        } else {
+            done();
+        }
+    };
+
+    /** Makes a deep copy of an object.
+     */
+    djstest.clone = function (object) {
+        if ( object === undefined ) {
+            return undefined;
+        } else if (object === null) {
+            return null;
+        } else if (typeof(object) !== 'object') {
+            return object;
+        } else {
+            var ret = {};
+            for(var key in object) {
+                if(object.hasOwnProperty(key)) {
+                    ret[key] = this.clone(object[key]);
+                }
+            }
+            return ret;
+        }
+        throw("Error cloning an object");
+    };
+
+    /** Destroys the cache and then completes the test
+     * @param cache - The cache to destroy
+     */
+    djstest.destroyCacheAndDone = function (cache) {
+     
+        cache.clear().then(function () {
+            djstest.done();
+        }, function (err) {
+            djstest.fail("Failed to destroy cache: " + djstest.toString(err));
+            djstest.done();
+        });
+    };
+
+    /** Indicates that the currently running test has finished.
+     */
+    djstest.done = function () {
+      
+        QUnit.start();
+    };
+
+    /** Test passes if and only if an exception is thrown.
+     */
+    djstest.expectException = function (testFunction, message) {
+     
+        try {
+            testFunction();
+            djstest.fail("Expected exception but function succeeded: " + " " + 
message);
+        }
+        catch (e) {
+            // Swallow exception.
+            djstest.pass("Thrown exception expected");
+        }
+    };
+
+    /** Indicates the expected number of asserts, fails test if number is not 
met.
+     * @param {Number} asserts - Number of asserts expected in test.
+     */
+    djstest.assertsExpected = function (asserts) {
+        
+        expect(asserts);
+    };
+    /** Marks the current test as failed.
+     * @param {String} message - Failure message.
+     */
+    djstest.fail = function (message) {
+
+        QUnit.ok(false, message);
+    };
+
+    /** Returns a function that when invoked will fail this test and be done 
with it.
+     * @param {String} message - Failure message.
+     * @param {Function} [cleanupCallback] - 
+     * @returns {Function} A new function.
+     */
+    djstest.failAndDoneCallback = function (message, cleanupCallback) {
+
+        return function (err) {
+            message = "" + message + (err) ? JSON.stringify(err) : "";
+            djstest.fail(message);
+            if (cleanupCallback) {
+                try {
+                    cleanupCallback();
+                } catch (e) {
+                    djstest.fail("error during cleanupCallback: " + 
JSON.stringify(e));
+                }
+            }
+
+            djstest.done();
+        };
+    };
+
+    /** Logs a test message.
+     * @param {String} message - Test message.
+     */
+    djstest.log = function (message) {
+
+        var context = { result: true, actual: true, expected: true, message: 
message };
+        QUnit.log(context);
+    };
+
+    /** Marks the current test as failed.
+     * @param {String} message - Failure message.
+     */
+    djstest.pass = function (message) {
+        QUnit.ok(true, message);
+    };
+
+    /** Dumps the object as a string
+     * @param {Object} obj - Object to dump
+     */
+    djstest.toString = function (obj) {
+
+        return QUnit.jsDump.parse(obj);
+    };
+
+    /** Executes the function, pausing test execution until the callback is 
called
+     * @param {Function} fn - Function to execute; takes one parameter which 
is the callback
+     * This function is typically used in asynchronous setup/teardown 
methods</remarks>
+     */
+    djstest.wait = function (fn) {
+        QUnit.stop();
+        fn(function () {
+            QUnit.start();
+        });
+    };
+
+    return djstest;
+};
+
+//export djstest
+
+if (typeof window !== 'undefined') {
+    //expose to browsers window object
+    if ( window.djstest === undefined) {
+        window.djstest = init();
+    } else {
+        var tmp = init();
+        $.extend( window.djstest,tmp);
+    }
+} else {
+    //expose in commonjs style
+    module.exports = init();
+}
+

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

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

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

Reply via email to