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
