text: /*!
   * ${copyright}
   */
  sap.ui.define([
  	"jquery.sap.global",
  	"sap/base/Log",
  	"sap/base/util/deepEqual",
  	"sap/base/util/merge",
  	"sap/base/util/uid",
  	"sap/ui/base/SyncPromise",
  	"sap/ui/model/odata/v4/lib/_Helper",
  	"sap/ui/test/TestUtils",
  	"sap/ui/thirdparty/URI"
  ], function (jQuery, Log, deepEqual, merge, uid, SyncPromise, _Helper, TestUtils, URI) {
  	"use strict";
  
  	var sClassName = "sap.ui.model.odata.v4.lib._Helper";
  
  	/**
  	 * Checks the given cloned error according to the given expectations.
  	 *
  	 * @param {object} assert - The QUnit assert object
  	 * @param {Error} oOriginal - The original error
  	 * @param {Error} oClone - The cloned error
  	 * @param {object} oExpectedInnerError - The expected inner error
  	 * @param {string} sUrl - The expected request URL
  	 * @param {string} sResourcePath - The expected resource path
  	 */
  	function checkClonedError(assert, oOriginal, oClone, oExpectedInnerError, sUrl, sResourcePath) {
  		assert.notStrictEqual(oClone, oOriginal);
  		assert.ok(oClone instanceof Error);
  		assert.strictEqual(oClone.message, "Message");
  		assert.strictEqual(oClone.requestUrl, sUrl);
  		assert.strictEqual(oClone.resourcePath, sResourcePath);
  		assert.strictEqual(oClone.status, 500);
  		assert.strictEqual(oClone.statusText, "Internal Server Error");
  		assert.notStrictEqual(oClone.error, oOriginal.error);
  		assert.deepEqual(oClone.error, oExpectedInnerError);
  	}
  
  	/**
  	 * Returns a mock "fnFetchMetadata" (see _Requestor#getModelInterface) which returns metadata
  	 * for a meta path according to the given map.
  	 *
  	 * @param {object} mMetaPath2Type
  	 *   A map from meta path to symbolic type, for example
  	 *   <code>{"/Me/A" : "", "/Me/to1" : "1", "/Me/toN" : "N"}</code>, where "" means structural
  	 *   property, "N" means collection-valued navigation property and "1" means navigation property
  	 * @returns {function}
  	 *   "fnFetchMetadata"
  	 */
  	function getFetchMetadata(mMetaPath2Type) {
  		return function (sMetaPath) {
  			var vResult = mMetaPath2Type[sMetaPath];
  
  			switch (vResult) {
  				case "":
  					return SyncPromise.resolve({
  						$kind : "Property"
  					});
  
  				case "1":
  					return SyncPromise.resolve({
  						$kind : "NavigationProperty"
  //						$isCollection : false
  					});
  
  				case "N":
  					return SyncPromise.resolve({
  						$kind : "NavigationProperty",
  						$isCollection : true
  					});
  
  				default:
  					if (vResult && typeof vResult === "object") {
  						return SyncPromise.resolve(vResult);
  					}
  					throw new Error(sMetaPath);
  			}
  		};
  	}
  
  	//*********************************************************************************************
  	QUnit.module("sap.ui.model.odata.v4.lib._Helper", {
  		beforeEach : function () {
  			this.oLogMock = this.mock(Log);
  			this.oLogMock.expects("warning").never();
  			this.oLogMock.expects("error").never();
  		}
  	});
  
  	//*********************************************************************************************
  	QUnit.test("createGetMethod, not throwing", function (assert) {
  		var aArguments = ["foo", "bar"],
  			oResult = {},
  			oSyncPromise = SyncPromise.resolve(oResult),
  			oContext = {
  				fetch : function () {
  					assert.strictEqual(this, oContext);
  					assert.deepEqual(Array.prototype.slice.call(arguments), aArguments);
  					return oSyncPromise;
  				}
  			},
  			fnGet;
  
  		// code under test
  		// Note: passing the function's name instead of reference allows for dynamic dispatch, thus
  		// making a mock for "fetch*" possible in the first place
  		fnGet = _Helper.createGetMethod("fetch");
  
  		assert.strictEqual(fnGet.apply(oContext, aArguments), oResult);
  		this.mock(oSyncPromise).expects("isFulfilled").returns(false);
  		assert.strictEqual(fnGet.apply(oContext, aArguments), undefined);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("createGetMethod, throwing", function (assert) {
  		var aArguments = ["foo", "bar"],
  			oResult = {},
  			oSyncPromise = SyncPromise.resolve(oResult),
  			oContext = {
  				fetch : function () {
  					assert.strictEqual(this, oContext);
  					assert.deepEqual(Array.prototype.slice.call(arguments), aArguments);
  					return oSyncPromise;
  				}
  			},
  			fnGet,
  			oSyncPromiseMock = this.mock(oSyncPromise);
  
  		// code under test
  		fnGet = _Helper.createGetMethod("fetch", true);
  
  		// fulfilled
  		assert.strictEqual(fnGet.apply(oContext, aArguments), oResult);
  
  		// pending
  		oSyncPromiseMock.expects("isFulfilled").returns(false);
  		oSyncPromiseMock.expects("isRejected").returns(false);
  		assert.throws(function () {
  			fnGet.apply(oContext, aArguments);
  		}, new Error("Result pending"));
  
  		// verify and restore
  		oSyncPromiseMock.verify();
  		oSyncPromiseMock = this.mock(oSyncPromise);
  
  		// rejected
  		oSyncPromiseMock.expects("isFulfilled").returns(false);
  		oSyncPromiseMock.expects("isRejected").returns(true);
  		oSyncPromiseMock.expects("caught");
  		assert.throws(function () {
  			fnGet.apply(oContext, aArguments);
  		}, oResult);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("createRequestMethod", function (assert) {
  		var aArguments = ["foo", "bar"],
  			oResult = {},
  			oSyncPromise = SyncPromise.resolve(),
  			oContext = {
  				fetch : function () {
  					assert.strictEqual(this, oContext);
  					assert.deepEqual(Array.prototype.slice.call(arguments), aArguments);
  					return oSyncPromise;
  				}
  			},
  			fnRequest;
  
  		this.mock(Promise).expects("resolve")
  			.withExactArgs(sinon.match.same(oSyncPromise)).returns(oResult);
  
  		// code under test
  		fnRequest = _Helper.createRequestMethod("fetch");
  
  		assert.strictEqual(fnRequest.apply(oContext, aArguments), oResult);
  	});
  
  	//*********************************************************************************************
  	[{
  		message : "CSRF token validation failed",
  		"response" : {
  			"headers" : {
  				"Content-Type" : "text/plain;charset=utf-8",
  				"x-csrf-token" : "Required"
  			},
  			"responseText" : "CSRF token validation failed",
  			"status" : 403,
  			"statusText" : "Forbidden"
  		}
  	}, {
  		message : "message: 401 Unauthorized",
  		"response" : {
  			"headers" : {
  				"Content-Type" : "text/html;charset=utf-8"
  			},
  			"responseText" : "<html>...</html>",
  			"status" : 401,
  			"statusText" : "Unauthorized"
  		}
  	}, {
  		// OData V4 error response body as JSON
  		// "The error response MUST be a single JSON object. This object MUST have a
  		// single name/value pair named error. The value must be a JSON object."
  		body : {
  			"error" : {
  				"code" : "/IWBEP/CM_V4_RUNTIME/021",
  				"message" :
  					// Note: "a human-readable, language-dependent representation of the error"
  					"The state of the resource (entity) was already changed (If-Match)",
  				"@Common.ExceptionCategory" : "Client_Error",
  				"@Common.Application" : {
  					"ComponentId" : "OPU-BSE-BEP",
  					"ServiceRepository" : "DEFAULT",
  					"ServiceId" : "/IWBEP/TEA_BUSI",
  					"ServiceVersion" : "0001"
  				},
  				"@Common.TransactionId" : "5617D1F235DE73F0E10000000A60180C",
  				"@Common.Timestamp" : "20151009142600.103179",
  				"@Common.ErrorResolution" : {
  					"Analysis" : "Run transaction /IWFND/ERROR_LOG [...]",
  					"Note" : "See SAP Note 1797736 for error analysis "
  						+ "(https://service.sap.com/sap/support/notes/1797736)"
  				}
  			}
  		},
  		isConcurrentModification : true,
  		message : "The state of the resource (entity) was already changed (If-Match)",
  		"response" : {
  			"headers" : {
  				"Content-Type" : "application/json; odata.metadata=minimal;charset=utf-8"
  			},
  //			"responseText" : JSON.stringify(this.body)
  			"status" : 412,
  			"statusText" : "Precondition Failed"
  		}
  	}, {
  		message : "message: 999 Invalid JSON",
  		"response" : {
  			"headers" : {
  				"Content-Type" : "application/json"
  			},
  			"responseText" : "<html>...</html>",
  			"status" : 999,
  			"statusText" : "Invalid JSON"
  		},
  		warning : sinon.match(/SyntaxError/)
  	}, {
  		message : "message: 403 Forbidden",
  		"response" : {
  			"headers" : {
  				"Content-Type" : "text/plain-not-quite-right"
  			},
  			"status" : 403,
  			"statusText" : "Forbidden",
  			"responseText" : "ignore this!"
  		}
  	}, {
  		message : "Network error",
  		"response" : {
  			"headers" : {},
  			"status" : 0,
  			"statusText" : "error"
  		}
  	}, {
  		message : "message: 404 Not Found",
  		"response" : {
  			"headers" : {},
  			"status" : 404,
  			"statusText" : "Not Found"
  		}
  	}, {
  		// V2 error
  		body : {
  			error : {
  				code : "0050569259751EE4BA9710043F8A5115",
  				message : {
  					lang : "en",
  					value : "An unknown internal server error occurred"
  				}
  			}
  		},
  		message : "An unknown internal server error occurred",
  		"response" : {
  			"headers" : {
  				"Content-Type" : "application/json;charset=UTF-8"
  			},
  			"status" : 500,
  			"statusText" : "Internal Server Error"
  		}
  	}].forEach(function (oFixture) {
  		QUnit.test("createError: " + oFixture.message, function (assert) {
  			var oError,
  				jqXHR = {
  					getResponseHeader : function (sName) {
  						return oFixture.response.headers[sName];
  					},
  					"status" : oFixture.response.status,
  					"statusText" : oFixture.response.statusText,
  					"responseText" : oFixture.response.responseText || JSON.stringify(oFixture.body)
  				};
  
  			if (oFixture.warning) {
  				this.oLogMock.expects("warning").withExactArgs(oFixture.warning,
  					oFixture.response.responseText, "sap.ui.model.odata.v4.lib._Helper");
  			}
  
  			oError = _Helper.createError(jqXHR, "message", "/request/path", "original/path");
  
  			assert.ok(oError instanceof Error);
  			assert.deepEqual(oError.error, oFixture.body && oFixture.body.error);
  			assert.strictEqual(oError.isConcurrentModification, oFixture.isConcurrentModification);
  			assert.strictEqual(oError.message, oFixture.message);
  			assert.strictEqual(oError.status, oFixture.response.status);
  			assert.strictEqual(oError.statusText, oFixture.response.statusText);
  			assert.strictEqual(oError.requestUrl, "/request/path");
  			assert.strictEqual(oError.resourcePath, "original/path");
  		});
  	});
  
  	//*********************************************************************************************
  [
  	{message : {/* any original message */}},
  	{"@$ui5.originalMessage" : {message : {/* any original message */}}}
  ].forEach(function (oFixture, i) {
  	QUnit.test("createTechnicalDetails," + i, function (assert) {
  		var oClone = {foo : "bar"},
  			oResult;
  
  		this.mock(_Helper).expects("publicClone")
  			.withExactArgs(sinon.match.same(i === 0 ? oFixture : oFixture["@$ui5.originalMessage"]))
  			.returns(oClone);
  
  		// code under test
  		oResult = _Helper.createTechnicalDetails(oFixture);
  
  		assert.deepEqual(oResult, {originalMessage : oClone});
  
  		// code under test
  		assert.strictEqual(oResult.originalMessage, oClone);
  
  		// code under test (take care that further accesses point to the same object)
  		assert.strictEqual(oResult.originalMessage, oClone);
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("createTechnicalDetails with a JS Error instance", function (assert) {
  		this.mock(_Helper).expects("publicClone").never();
  
  		// code under test
  		assert.deepEqual(_Helper.createTechnicalDetails(new Error()), {});
  
  		// code under test
  		assert.deepEqual(_Helper.createTechnicalDetails({"@$ui5.originalMessage" : new Error()}),
  			{});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("createTechnicalDetails with @$ui5.error, no status", function (assert) {
  		var oMessage = new Error();
  
  		oMessage["@$ui5.error"] = {};
  		this.mock(_Helper).expects("publicClone").never();
  
  		// code under test
  		assert.deepEqual(_Helper.createTechnicalDetails(oMessage), {});
  	});
  
  //*********************************************************************************************
  // Note: if error has a cause, that cause also has a status; @see _Requestor#processBatch
  [false, true].forEach(function (bCause) {
  	var sTitle = "createTechnicalDetails with @$ui5.error and status; cause = " + bCause;
  
  	QUnit.test(sTitle, function (assert) {
  		var oError = {status : 123},
  			oMessage = new Error();
  
  		oMessage["@$ui5.error"] = bCause ? {cause : oError} : oError;
  		this.mock(_Helper).expects("publicClone").never();
  
  		// code under test
  		assert.deepEqual(_Helper.createTechnicalDetails(oMessage), {httpStatus : 123});
  	});
  });
  
  	//*********************************************************************************************
  [false, true].forEach(function (bCause) {
  	var sTitle = "createTechnicalDetails: isConcurrentModification; cause = " + bCause;
  
  	QUnit.test(sTitle, function (assert) {
  		var oError = {
  				isConcurrentModification : true,
  				status : 412
  			},
  			oMessage = new Error();
  
  		oMessage["@$ui5.error"] = bCause ? {cause : oError} : oError;
  		this.mock(_Helper).expects("publicClone").never();
  
  		// code under test
  		assert.deepEqual(_Helper.createTechnicalDetails(oMessage), {
  			httpStatus : 412,
  			isConcurrentModification : true
  		});
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("encode", function (assert) {
  		var sUnchanged = "foo$,/:?@();";
  
  		assert.strictEqual(_Helper.encode(sUnchanged, false), sUnchanged);
  		assert.strictEqual(_Helper.encode(sUnchanged, true), sUnchanged);
  
  		assert.strictEqual(_Helper.encode("€_&_=_#_+", false), "%E2%82%AC_%26_=_%23_%2B");
  		assert.strictEqual(_Helper.encode("€_&_=_#_+", true), "%E2%82%AC_%26_%3D_%23_%2B");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("encodePair", function (assert) {
  		var sEncoded,
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("encode").withExactArgs("key", true).returns("~key~");
  		oHelperMock.expects("encode").withExactArgs("value", false).returns("~value~");
  
  		sEncoded = _Helper.encodePair("key", "value");
  		assert.strictEqual(sEncoded, "~key~=~value~");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("buildQuery: no query", function (assert) {
  		assert.strictEqual(_Helper.buildQuery(), "");
  		assert.strictEqual(_Helper.buildQuery({}), "");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("buildQuery: query", function (assert) {
  		var sEncoded,
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("encodePair").withExactArgs("a", "b").returns("a=b");
  		oHelperMock.expects("encodePair").withExactArgs("c", "d").returns("c=d");
  		oHelperMock.expects("encodePair").withExactArgs("c", "e").returns("c=e");
  
  		sEncoded = _Helper.buildQuery({a : "b", c : ["d", "e"]});
  		assert.strictEqual(sEncoded, "?a=b&c=d&c=e");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("buildQuery and decoding via URI.js", function (assert) {
  		var sComplexString = "",
  			i,
  			mParameters = {},
  			sUri;
  
  		for (i = 32; i < 127; i += 1) {
  			sComplexString = sComplexString + String.fromCharCode(i);
  		}
  
  		sUri = "/" + _Helper.buildQuery({foo : sComplexString});
  
  		// decode via URI.js
  		assert.strictEqual(new URI(sUri).search(true).foo, sComplexString);
  
  		mParameters[sComplexString] = "foo";
  		sUri = "/" + _Helper.buildQuery(mParameters);
  
  		// decode via URI.js
  		assert.strictEqual(new URI(sUri).search(true)[sComplexString], "foo");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("isSafeInteger", function (assert) {
  		function localTest(sNumber, bValue) {
  			assert.strictEqual(_Helper.isSafeInteger(sNumber), bValue, sNumber);
  		}
  		localTest(0, true);
  		localTest((Math.pow(2, 53) - 1), true);
  		localTest(Math.pow(2, 53), false);
  		localTest(1 - Math.pow(2, 53), true);
  		localTest(-Math.pow(2, 53), false);
  		localTest("foo", false);
  		localTest(3.14, false);
  		localTest(null, false);
  		localTest(undefined, false);
  		localTest(NaN, false);
  		localTest(Infinity, false);
  		localTest(-Infinity, false);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("fireChange: no listeners", function () {
  		// code under test
  		_Helper.fireChange({}, "path/to/property", {});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("fireChange: multiple listeners", function () {
  		var oChangeListener0 = {onChange : function () {}},
  			oChangeListener1 = {onChange : function () {}},
  			oChangeListener2 = {onChange : function () {}},
  			vValue = {};
  
  		this.mock(oChangeListener0).expects("onChange").withExactArgs(sinon.match.same(vValue));
  		this.mock(oChangeListener1).expects("onChange").withExactArgs(sinon.match.same(vValue));
  		this.mock(oChangeListener2).expects("onChange").withExactArgs(sinon.match.same(vValue));
  
  		// code under test
  		_Helper.fireChange({
  				"path/to/property" : [oChangeListener0, oChangeListener1, oChangeListener2]
  			}, "path/to/property", vValue);
  	});
  
  	//*********************************************************************************************
  	[false, true].forEach(function (bRemove) {
  		QUnit.test("fireChanges: null value, bRemove: " + bRemove, function () {
  			var mChangeListeners = {},
  				oHelperMock = this.mock(_Helper),
  				oValue = {Foo : null};
  
  			oHelperMock.expects("fireChange")
  				.withExactArgs(sinon.match.same(mChangeListeners), "path/to/object/Foo",
  					bRemove ? undefined : null);
  			oHelperMock.expects("fireChange")
  				.withExactArgs(sinon.match.same(mChangeListeners), "path/to/object",
  					bRemove ? undefined : sinon.match.same(oValue));
  
  			// code under test
  			_Helper.fireChanges(mChangeListeners, "path/to/object", oValue, bRemove);
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("formatLiteral", function (assert) {
  		assert.throws(function () {
  			_Helper.formatLiteral();
  		}, new Error("Illegal value: undefined"));
  
  		assert.strictEqual(_Helper.formatLiteral(null), "null"); // type must not matter
  	});
  
  	//*********************************************************************************************
  	// t: the tested type
  	// v: the value to format
  	// e: the literal (if different to v)
  	[
  		{t : "Edm.Binary", v : "1qkYNh/P5uvZ0zA+siScD=", l : "binary'1qkYNh/P5uvZ0zA+siScD='"},
  		{t : "Edm.Boolean", v : true, l : "true"},
  		{t : "Edm.Byte", v : 255, l : "255"},
  		{t : "Edm.Date", v : "2016-01-19"},
  		{t : "Edm.DateTimeOffset", v : "2016-01-13T14:08:31Z"},
  		{t : "Edm.Decimal", v : "-255.55"},
  		{t : "Edm.Double", v : 3.14, l : "3.14"},
  		{t : "Edm.Double", v : "INF"},
  		{t : "Edm.Duration", v : "P1DT0H0M0S", l : "duration'P1DT0H0M0S'"},
  		{t : "Edm.Guid", v : "936DA01F-9ABD-4D9D-80C7-02AF85C822A8"},
  		{t : "Edm.Int16", v : -32768, l : "-32768"},
  		{t : "Edm.Int32", v : 2147483647, l : "2147483647"},
  		{t : "Edm.Int64", v : "12345678901234568"},
  		{t : "Edm.SByte", v : -128, l : "-128"},
  		// Note: the internal representation of NaN/Infinity/-Infinity in the ODataModel
  		// is "NaN", "INF" and "-INF".
  		// That is how it comes from the server and it is not possible to change the model values
  		// to the JS representation Infinity,-Infinity or NaN
  		{t : "Edm.Single", v : "NaN"},
  		{t : "Edm.Single", v : "-INF"},
  		{t : "Edm.String", v : "foo", l : "'foo'"},
  		{t : "Edm.String", v : "string with 'quote'", l : "'string with ''quote'''"},
  		{t : "Edm.String", v : "more ''quotes''", l : "'more ''''quotes'''''"},
  		{t : "Edm.String", v : "s'om\"e", l : "'s''om\"e'"},
  		{t : "Edm.String", v : null, l : "null"},
  		{t : "Edm.TimeOfDay", v : "18:59:59.999"}
  	].forEach(function (oFixture) {
  		var sTitle = "formatLiteral/parseLiteral: " + oFixture.t + " " +  oFixture.v;
  		QUnit.test(sTitle, function (assert) {
  			var sLiteral = oFixture.l || oFixture.v;
  
  			assert.strictEqual(_Helper.formatLiteral(oFixture.v, oFixture.t), sLiteral);
  
  			switch (oFixture.t) {
  				case "Edm.Binary":
  				case "Edm.Duration":
  					// not supported
  					break;
  				default:
  					assert.strictEqual(_Helper.parseLiteral(sLiteral, oFixture.t, "path"),
  						oFixture.v);
  			}
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("formatLiteral: error case", function (assert) {
  		assert.throws(
  			function () { _Helper.formatLiteral("foo", "Edm.bar"); },
  			new Error("Unsupported type: Edm.bar")
  		);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("parseLiteral: null", function (assert) {
  		assert.strictEqual(_Helper.parseLiteral("null", "Any.Type"), null);
  	});
  
  	//*********************************************************************************************
  	["Edm.Binary", "Edm.Duration", "Edm.bar"].forEach(function (sType) {
  		QUnit.test("parseLiteral: unsupported " + sType, function (assert) {
  			assert.throws(function () {
  				_Helper.parseLiteral("foo", sType, "path/to/property");
  			}, new Error("path/to/property: Unsupported type: " + sType));
  		});
  	});
  
  	//*********************************************************************************************
  	// t: the tested type
  	// l: the literal to parse
  	[
  		{t : "Edm.Byte", l : "ten"},
  		{t : "Edm.Int16", l : "ten"},
  		{t : "Edm.Int32", l : "ten"},
  		{t : "Edm.SByte", l : "ten"},
  		{t : "Edm.Double", l : "Pi"},
  		{t : "Edm.Double", l : "Infinity"},
  		{t : "Edm.Single", l : "Pi"},
  		{t : "Edm.Single", l : "-Infinity"}
  	].forEach(function (oFixture) {
  		QUnit.test("parseLiteral: error: " + oFixture.t + " " +  oFixture.l, function (assert) {
  			assert.throws(function () {
  				_Helper.parseLiteral(oFixture.l, oFixture.t, "path/to/property");
  			}, new Error("path/to/property: Not a valid " + oFixture.t + " literal: "
  				+ oFixture.l));
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("buildPath", function (assert) {
  		assert.strictEqual(_Helper.buildPath(), "");
  		assert.strictEqual(_Helper.buildPath("base"), "base");
  		assert.strictEqual(_Helper.buildPath("base", "relative"), "base/relative");
  		assert.strictEqual(_Helper.buildPath("base", ""), "base");
  		assert.strictEqual(_Helper.buildPath("", "relative"), "relative");
  		assert.strictEqual(_Helper.buildPath("base", undefined, "relative"), "base/relative");
  		assert.strictEqual(_Helper.buildPath("base", 42, "relative"), "base/42/relative");
  		assert.strictEqual(_Helper.buildPath("base", 0, "relative"), "base/0/relative");
  		assert.strictEqual(_Helper.buildPath("/", "relative"), "/relative");
  		assert.strictEqual(_Helper.buildPath("/base", "relative"), "/base/relative");
  		assert.strictEqual(_Helper.buildPath("base", "('predicate')"), "base('predicate')");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: simple", function (assert) {
  		var oCacheData = {
  				"@$ui5._" : {predicate : "('1')"},
  				DeliveryDate : null,
  				SalesOrderItemID : "000100",
  				Note : "old",
  				AnotherNote : null
  			},
  			mChangeListeners = {},
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "SO_2_SOITEM/Note", "new");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "SO_2_SOITEM/AnotherNote",
  				"newAnotherNote");
  
  		// code under test
  		_Helper.updateExisting(mChangeListeners, "SO_2_SOITEM", oCacheData, {
  			DeliveryDate : null,
  			Note : "new",
  			Foo : "bar",
  			AnotherNote : "newAnotherNote"
  		});
  
  		assert.deepEqual(oCacheData, {
  			"@$ui5._" : {predicate : "('1')"},
  			DeliveryDate : null,
  			SalesOrderItemID : "000100",
  			Note : "new",
  			AnotherNote : "newAnotherNote"
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: structured", function (assert) {
  		var oAdvertisedAction = {title : "My Title"},
  			oCacheData = {
  				BusinessPartnerID : "42",
  				Address : {
  					City : "Walldorf",
  					PostalCode : "69190"
  				}
  			},
  			mChangeListeners = {},
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("fireChange").withExactArgs(sinon.match.same(mChangeListeners),
  			"SO_2_BP/Address/City", "Heidelberg");
  
  		// code under test: update cache with the value the user entered
  		_Helper.updateExisting(mChangeListeners, "SO_2_BP", oCacheData, {
  			Address : {
  				City : "Heidelberg"
  			}
  		});
  
  		assert.deepEqual(oCacheData, {
  			BusinessPartnerID : "42",
  			Address : {
  				City : "Heidelberg",
  				PostalCode : "69190"
  			}
  		});
  
  		oHelperMock.expects("fireChange").withExactArgs(sinon.match.same(mChangeListeners),
  			"SO_2_BP/Address/PostalCode", "69115");
  		oHelperMock.expects("fireChanges").withExactArgs(sinon.match.same(mChangeListeners),
  			"SO_2_BP/#foo.bar.AcBaz", sinon.match.same(oAdvertisedAction), false);
  
  		// code under test: update cache with the patch result
  		_Helper.updateExisting(mChangeListeners, "SO_2_BP", oCacheData, {
  			"#foo.bar.AcBaz" : oAdvertisedAction,
  			BusinessPartnerID : "42",
  			Address : {
  				City : "Heidelberg",
  				PostalCode : "69115"
  			}
  		});
  
  		assert.deepEqual(oCacheData, {
  			"#foo.bar.AcBaz" : oAdvertisedAction,
  			BusinessPartnerID : "42",
  			Address : {
  				City : "Heidelberg",
  				PostalCode : "69115"
  			}
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: changed advertised action", function (assert) {
  		var oAdvertisedAction = {title : "My Title"},
  			oCacheData = {"#foo.bar.AcBaz" : oAdvertisedAction},
  			mChangeListeners = {},
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("fireChange").withExactArgs(sinon.match.same(mChangeListeners),
  			"SO_2_BP/#foo.bar.AcBaz/title", "My New Title");
  		oHelperMock.expects("fireChanges").never();
  
  		// code under test: update cache with the patch result
  		_Helper.updateExisting(mChangeListeners, "SO_2_BP", oCacheData, {
  			"#foo.bar.AcBaz" : {title : "My New Title"}
  		});
  
  		assert.deepEqual(oCacheData, {
  			"#foo.bar.AcBaz" : {title : "My New Title"}
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: removed advertised action", function (assert) {
  		var oAdvertisedAction = {title : "My Title"},
  			oCacheData = {"#foo.bar.AcBaz" : oAdvertisedAction},
  			mChangeListeners = {},
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("fireChanges").withExactArgs(sinon.match.same(mChangeListeners),
  			"SO_2_BP/#foo.bar.AcBaz", sinon.match.same(oAdvertisedAction), true);
  
  		// code under test: update cache with the patch result
  		_Helper.updateExisting(mChangeListeners, "SO_2_BP", oCacheData, {});
  
  		assert.deepEqual(oCacheData, {
  			"#foo.bar.AcBaz" : undefined
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: collection valued properties (messages)", function (assert) {
  		var aNoMessages = [],
  			aMessages = [{
  				"code" : "42",
  				"longtextUrl" : "any/URL",
  				"message" : "message 1",
  				"transition" : false,
  				"target" : "Foo",
  				"numericSeverity" : 3
  			}, {
  				"code" : "17",
  				"longtextUrl" : "any/URL/2",
  				"message" : "message 2",
  				"transition" : true,
  				"target" : "Bar",
  				"numericSeverity" : 4
  			}],
  			sMessages,
  			oCacheData = {
  				BusinessPartnerID : "42",
  				"__CT__FAKE__Message" : {
  					"__FAKE__Messages" : aNoMessages
  				}
  			};
  
  		aNoMessages.$count = aNoMessages.length;
  		aMessages.$count = aMessages.length;
  		sMessages = JSON.stringify(aMessages);
  
  		// code under test
  		_Helper.updateExisting(null, "SO_2_BP", oCacheData, {
  			"__CT__FAKE__Message" : {
  				"__FAKE__Messages" : aMessages
  			}
  		});
  
  		assert.strictEqual(JSON.stringify(oCacheData["__CT__FAKE__Message"]["__FAKE__Messages"]),
  			sMessages);
  		assert.strictEqual(oCacheData["__CT__FAKE__Message"]["__FAKE__Messages"].$count, 2);
  
  		// code under test
  		_Helper.updateExisting({}, "SO_2_BP", oCacheData, {
  			"__CT__FAKE__Message" : {
  				"__FAKE__Messages" : aNoMessages
  			}
  		});
  
  		assert.deepEqual(oCacheData["__CT__FAKE__Message"]["__FAKE__Messages"], []);
  		assert.strictEqual(oCacheData["__CT__FAKE__Message"]["__FAKE__Messages"].$count, 0);
  
  		//TODO change handling for collection valued properties (not supported yet)
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: remove structured attribute", function (assert) {
  		var mChangeListeners = {
  				"SO_2_BP/Address/City" : [{onChange : function () {}}],
  				"SO_2_BP/Address/Foo/Bar" : [{onChange : function () {}}]
  			},
  			oCacheData = {
  				BusinessPartnerID : "42",
  				Address : {
  					City : "Walldorf",
  					PostalCode : "69190",
  					Foo : {
  						Bar : "Baz"
  					}
  				}
  			};
  
  		this.mock(mChangeListeners["SO_2_BP/Address/City"][0]).expects("onChange")
  			.withExactArgs(undefined);
  		this.mock(mChangeListeners["SO_2_BP/Address/Foo/Bar"][0]).expects("onChange")
  			.withExactArgs(undefined);
  
  		_Helper.updateExisting(mChangeListeners, "SO_2_BP", oCacheData, {
  			BusinessPartnerID : "42",
  			Address : null
  		});
  
  		assert.deepEqual(oCacheData, {
  			BusinessPartnerID : "42",
  			Address : null
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: add structured attribute", function (assert) {
  		var mChangeListeners = {
  				"SO_2_BP/Address/City" : [{onChange : function () {}}],
  				"SO_2_BP/Address/Foo/Bar" : [{onChange : function () {}}]
  			},
  			oCacheData = {
  				BusinessPartnerID : "42",
  				Address : null
  			};
  
  		this.mock(mChangeListeners["SO_2_BP/Address/City"][0]).expects("onChange")
  			.withExactArgs("Walldorf");
  		this.mock(mChangeListeners["SO_2_BP/Address/Foo/Bar"][0]).expects("onChange")
  			.withExactArgs("Baz");
  
  		_Helper.updateExisting(mChangeListeners, "SO_2_BP", oCacheData, {
  			BusinessPartnerID : "42",
  			Address : {
  				City : "Walldorf",
  				PostalCode : "69190",
  				Foo : {
  					Bar : "Baz"
  				}
  			}
  		});
  
  		assert.deepEqual(oCacheData, {
  			BusinessPartnerID : "42",
  			Address : {
  				City : "Walldorf",
  				PostalCode : "69190",
  				Foo : {
  					Bar : "Baz"
  				}
  			}
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: check cache value in change handler", function (assert) {
  		var oCacheData = {
  				BusinessPartnerID : "42",
  				Address : {
  					City : "Walldorf",
  					Foo : {
  						Bar : "Baz"
  					}
  				}
  			},
  			mChangeListeners = {
  				"SO_2_BP/Address/City" : [{onChange : function () {
  					assert.strictEqual(oCacheData.Address.City, "St. Ingbert");
  				}}],
  				"SO_2_BP/Address/Foo/Bar" : [{onChange : function () {
  					assert.strictEqual(oCacheData.Address.Foo, null);
  				}}]
  			};
  
  		// code under test
  		_Helper.updateExisting(mChangeListeners, "SO_2_BP", oCacheData, {
  			BusinessPartnerID : "42",
  			Address : {
  				City : "St. Ingbert",
  				Foo : null
  			}
  		});
  
  		assert.deepEqual(oCacheData, {
  			BusinessPartnerID : "42",
  			Address : {
  				City : "St. Ingbert",
  				Foo :  null
  			}
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateExisting: empty value from PATCH response", function (assert) {
  		var oCacheData = {
  				SalesOrderItemID : "000100"
  			};
  
  		// code under test
  		_Helper.updateExisting({/*mChangeListeners*/}, "SO_2_SOITEM", oCacheData,
  			/*empty PATCH response*/undefined);
  
  		assert.deepEqual(oCacheData, {
  			SalesOrderItemID : "000100"
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("toArray", function (assert) {
  		var oObject = {},
  			aObjects = [oObject];
  
  		assert.deepEqual(_Helper.toArray(), []);
  		assert.deepEqual(_Helper.toArray(null), []);
  		assert.deepEqual(_Helper.toArray(""), [""]);
  		assert.deepEqual(_Helper.toArray("foo"), ["foo"]);
  		assert.deepEqual(_Helper.toArray(oObject), aObjects);
  		assert.strictEqual(_Helper.toArray(aObjects), aObjects);
  	});
  
  	//*********************************************************************************************
  	// Integration tests with real back end
  	if (TestUtils.isRealOData()) {
  		QUnit.test("Integration test for formatLiteral", function (assert) {
  			var done = assert.async(),
  			sResolvedServiceUrl = TestUtils.proxy(
  				"/sap/opu/odata4/sap/zui5_testv4/default/sap/zui5_epm_sample/0002/");
  
  			jQuery.ajax(sResolvedServiceUrl + "BusinessPartnerList?"
  				+ "$filter=CompanyName eq + " + _Helper.formatLiteral("Becker Berlin", "Edm.String")
  				, { method : "GET"}
  			).then(function (oData) {
  				assert.strictEqual(oData.value[0].CompanyName, "Becker Berlin");
  				done();
  			});
  		});
  	}
  
  	//*********************************************************************************************
  	[{
  		sKeyPredicate : "('4%2F2')",
  		mKeyProperties : {"ID" : "'4/2'"}
  	}, {
  		bKeepSingleProperty : true,
  		sKeyPredicate : "(ID='4%2F2')",
  		mKeyProperties : {"ID" : "'4/2'"}
  	}, {
  		sKeyPredicate : "(Bar=42,Fo%3Do='Walter%22s%20Win''s')",
  		mKeyProperties : {"Bar" : "42","Fo=o" : "'Walter\"s Win''s'"}
  	}, {
  		sKeyPredicate: undefined,
  		mKeyProperties: undefined
  	}].forEach(function (oFixture) {
  		QUnit.test("getKeyPredicate: " + oFixture.sKeyPredicate, function (assert) {
  			var oEntityInstance = {},
  				aKeyProperties = [],
  				sMetaPath = "~path~",
  				mTypeForMetaPath = {};
  
  			this.mock(_Helper).expects("getKeyProperties")
  				.withExactArgs(sinon.match.same(oEntityInstance), sMetaPath,
  					sinon.match.same(mTypeForMetaPath), sinon.match.same(aKeyProperties), true)
  				.returns(oFixture.mKeyProperties);
  
  			// code under test
  			assert.strictEqual(
  				_Helper.getKeyPredicate(oEntityInstance, sMetaPath, mTypeForMetaPath,
  					aKeyProperties, oFixture.bKeepSingleProperty),
  				oFixture.sKeyPredicate);
  		});
  	});
  
  	//*********************************************************************************************
  	[{
  		oEntityInstance : {ID : "42"},
  		oEntityType : {
  			$Key : ["ID"],
  			ID : {
  				$Type : "Edm.String"
  			}
  		},
  		mKeyProperties : {ID : "'42'"}
  	}, {
  		oEntityInstance : {
  			Bar : 42,
  			"Fo=o" : "Walter\"s Win's",
  			Baz : "foo"
  		},
  		oEntityType : {
  			$Key : ["Bar", "Fo=o"],
  			Bar : {
  				$Type : "Edm.Int16"
  			},
  			"Fo=o" : {
  				$Type : "Edm.String"
  			}
  		},
  		mKeyProperties : {
  			Bar : "42",
  			"Fo=o" : "'Walter\"s Win''s'"
  		}
  	}, {
  		oEntityInstance : {},
  		oEntityType : {
  			$Key : ["ID"],
  			ID : {
  				$Type : "Edm.String"
  			}
  		},
  		mKeyProperties : undefined
  	}, {
  		oEntityInstance : {
  			foo : "baz",
  			bar : "qux"
  		},
  		oEntityType : {
  			$Key : ["ID"],
  			bar : {
  				$Type : "Edm.String"
  			},
  			foo : {
  				$Type : "Edm.String"
  			}
  		},
  		mKeyProperties : {
  			foo : "'baz'",
  			bar : "'qux'"
  		},
  		aKeyProperties: ["foo", "bar"]
  
  	}].forEach(function (oFixture) {
  		QUnit.test("getKeyProperties: " + oFixture.mKeyProperties, function (assert) {
  			this.spy(_Helper, "formatLiteral");
  
  			// code under test
  			assert.deepEqual(
  				_Helper.getKeyProperties(oFixture.oEntityInstance, "~path~", {
  					"~path~" : oFixture.oEntityType
  				}, oFixture.aKeyProperties),
  				oFixture.mKeyProperties);
  
  			// check that formatPropertyAsLiteral() is called for each key property
  			oFixture.oEntityType.$Key.forEach(function (sProperty) {
  				if (sProperty in oFixture.oEntityInstance) {
  					sinon.assert.calledWithExactly(_Helper.formatLiteral,
  						sinon.match.same(oFixture.oEntityInstance[sProperty]),
  						sinon.match.same(oFixture.oEntityType[sProperty].$Type));
  				}
  			});
  		});
  	});
  
  	//*********************************************************************************************
  	[{
  		oResult : {"qux" : "~1", "bar/baz" : "~2"},
  		bReturnAlias : false
  	}, {
  		oResult : {qux : "~1", foo : "~2"},
  		bReturnAlias : true
  	}].forEach(function (oFixture) {
  		QUnit.test("getKeyProperties: bReturnAlias=" + oFixture.bReturnAlias, function (assert) {
  			var oComplexType = {
  					"baz": {
  						"$kind": "Property",
  						"$Type": "Edm.Int16"
  					}
  				},
  				oEntityInstance = {},
  				oEntityType = {
  					"$Key": ["qux", {"foo": "bar/baz"}],
  					"qux": {
  						"$kind": "Property",
  						"$Type": "Edm.String"
  					}
  				},
  				oHelperMock = this.mock(_Helper),
  				sMetaPath = "~path~",
  				mTypeForMetaPath = {
  					"~path~": oEntityType,
  					"~path~/bar": oComplexType
  				};
  
  			oHelperMock.expects("drillDown")
  				.withExactArgs(sinon.match.same(oEntityInstance), ["qux"]).returns("v1");
  			oHelperMock.expects("drillDown")
  				.withExactArgs(sinon.match.same(oEntityInstance), ["bar", "baz"]).returns("v2");
  			oHelperMock.expects("formatLiteral").withExactArgs("v1", "Edm.String").returns("~1");
  			oHelperMock.expects("formatLiteral").withExactArgs("v2", "Edm.Int16").returns("~2");
  
  			// code under test
  			assert.deepEqual(_Helper.getKeyProperties(oEntityInstance, sMetaPath, mTypeForMetaPath,
  				undefined, oFixture.bReturnAlias), oFixture.oResult);
  		});
  	});
  
  	//*********************************************************************************************
  	[false, true].forEach(function (bReturnAlias) {
  		QUnit.test("getKeyProperties throws, bReturnAlias = " + bReturnAlias, function (assert) {
  			assert.throws(function () {
  				// code under test
  				_Helper.getKeyProperties({}, "~path~", {"~path~" : {/*no $Key*/}}, bReturnAlias);
  			});
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getKeyFilter", function (assert) {
  		var oHelperMock = this.mock(_Helper),
  			oInstance = {},
  			aKeyProperties = [],
  			sMetaPath = {/*meta path*/},
  			mTypeForMetaPath = {};
  
  		oHelperMock.expects("getKeyProperties")
  			.withExactArgs(sinon.match.same(oInstance), sinon.match.same(sMetaPath),
  				sinon.match.same(mTypeForMetaPath), sinon.match.same(aKeyProperties))
  			.returns({key : 42});
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getKeyFilter(oInstance, sMetaPath, mTypeForMetaPath, aKeyProperties),
  			"key eq 42");
  
  		oHelperMock.expects("getKeyProperties")
  			.withExactArgs(sinon.match.same(oInstance), sinon.match.same(sMetaPath),
  				sinon.match.same(mTypeForMetaPath), sinon.match.same(aKeyProperties))
  			.returns({key1 : "'a'", key2 : "'b'"});
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getKeyFilter(oInstance, sMetaPath, mTypeForMetaPath, aKeyProperties),
  			"key1 eq 'a' and key2 eq 'b'");
  
  		oHelperMock.expects("getKeyProperties")
  			.withExactArgs(sinon.match.same(oInstance), sinon.match.same(sMetaPath),
  				sinon.match.same(mTypeForMetaPath), sinon.match.same(aKeyProperties))
  			.returns(undefined); // at least one key property is undefined
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getKeyFilter(oInstance, sMetaPath, mTypeForMetaPath, aKeyProperties),
  			undefined);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("namespace", function (assert) {
  		assert.strictEqual(_Helper.namespace("Products"), "");
  		assert.strictEqual(_Helper.namespace("zui5_epm_sample.Products"), "zui5_epm_sample");
  		assert.strictEqual(_Helper.namespace("zui5_epm_sample.v1.Products"), "zui5_epm_sample.v1");
  		assert.strictEqual(_Helper.namespace("zui5_epm_sample.v1.Products/Category/type.cast"),
  			"zui5_epm_sample.v1");
  		assert.strictEqual(
  			_Helper.namespace("zui5_epm_sample.v1.Action(ui5_epm_sample.v1.EnityType)/"),
  			"zui5_epm_sample.v1");
  		assert.strictEqual(
  			_Helper.namespace("zui5_epm_sample.v1.Action(ui5_epm_sample.v1.EnityType)/Country"),
  			"zui5_epm_sample.v1");
  		assert.strictEqual(
  			_Helper.namespace("zui5_epm_sample.v1.Action(Collection(ui5_epm_sample.v1.EnityType))"),
  			"zui5_epm_sample.v1");
  		assert.strictEqual(
  			_Helper.namespace("zui5_epm_sample.v1.Action(Collection(ui5_epm_sample.v1.EnityType))"
  				+ "/Country"),
  			"zui5_epm_sample.v1");
  		assert.strictEqual(
  			_Helper.namespace("zui5_epm_sample.v1.Action(Collection(ui5_epm_sample.v1.EnityType))"
  				+ "/Address/Country"),
  			"zui5_epm_sample.v1");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateSelected: simple/complex and not wanted properties", function (assert) {
  		var oCacheBefore = {
  				Address : {
  					City : "Walldorf"
  				},
  				ComplexNullable : null
  			},
  			oCacheAfter = {
  				PartnerId : "4711",
  				Address : {
  					City : "Walldorf",
  					GeoLocation : {
  						Latitude : "49.3",
  						Longitude : "8.6"
  					},
  					PostalCode : "69190",
  					Nullable : null
  				},
  				ComplexNullable : {
  					bar : null,
  					baz : null,
  					foo : "foo"
  				}
  			},
  			oChangeListener = {},
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/Address/GeoLocation/Latitude", "49.3");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/Address/GeoLocation/Longitude", "8.6");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/Address/Nullable", null);
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/Address/PostalCode", "69190");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/ComplexNullable/bar", null);
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/ComplexNullable/foo", "foo");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/PartnerId", "4711");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/@$ui5._/predicate", "('4711')").never();
  
  		// code under test
  		_Helper.updateSelected(oChangeListener, "SO_2_BP", oCacheBefore, {
  			PartnerId : "4711",
  			Address : {
  				City : "Walldorf",
  				GeoLocation : {
  					Latitude : "49.3",
  					Longitude : "8.6"
  				},
  				notWanted : "foo",
  				Nullable : null,
  				PostalCode : "69190"
  			},
  			ComplexNullable : {
  				bar : null,
  				baz : null,
  				foo : "foo"
  			},
  			notWanted : "bar"
  		}, [
  			"Address/City",
  			"Address/Foo/Bar",
  			"Address/GeoLocation/Latitude",
  			"Address/GeoLocation/Longitude",
  			"Address/Nullable",
  			"Address/PostalCode",
  			"ComplexNullable/bar",
  			"ComplexNullable/baz/belowBaz",
  			"ComplexNullable/foo",
  			"PartnerId"
  		]);
  
  		assert.deepEqual(oCacheBefore, oCacheAfter);
  
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/CompanyName", "SAP");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(oChangeListener, "SO_2_BP/@$ui5._/predicate", "('4711')").never();
  		oCacheBefore = {};
  		oCacheAfter = {CompanyName : "SAP"};
  
  		// code under test (without predicate)
  		_Helper.updateSelected(oChangeListener, "SO_2_BP", oCacheBefore, {CompanyName : "SAP" },
  			["CompanyName"]);
  
  		assert.deepEqual(oCacheBefore, oCacheAfter);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateSelected", function (assert) {
  		var mChangeListener = {},
  			oHelperMock = this.mock(_Helper),
  			oNewValue = {
  				changed : "new",
  				unchanged : "same",
  				fromNull : "new",
  				toNull : null,
  				unselected : "new",
  				collection : [],
  				nested : {
  					changed : "new",
  					unchanged : "same",
  					fromNull : "new",
  					toNull : null,
  					unselected : "new"
  				},
  				structuredFromNull : {
  					changed : "new",
  					toNull : null,
  					nested : {
  						changed : "new"
  					},
  					structuredToNull : null,
  					unselected : "new",
  					collection : []
  				},
  				structuredNull : null,
  				structuredToNull : null
  			},
  			oOldValue = {
  				changed : "old",
  				unchanged : "same",
  				fromNull : null,
  				toNull : "old",
  				collection : [],
  				nested : {
  					changed : "old",
  					unchanged : "same",
  					fromNull : null,
  					toNull : "old"
  				},
  				structuredFromNull : null,
  				structuredNull : null,
  				structuredToNull : {
  					changed : "old",
  					fromNull : null,
  					nested : {
  						changed : "old"
  					}
  				}
  			},
  			aSelect = [
  				"changed",
  				"collection",
  				"fromNull",
  				"toNull",
  				"unchanged",
  				"nested/changed",
  				"nested/fromNull",
  				"nested/toNull",
  				"nested/unchanged",
  				"structuredFromNull/changed",
  				"structuredFromNull/collection",
  				"structuredFromNull/toNull",
  				"structuredFromNull/structuredToNull/unseen",
  				"structuredFromNull/nested/changed",
  				"structuredNull/unseen",
  				"structuredToNull/changed",
  				"structuredToNull/fromNull",
  				"structuredToNull/nested/changed",
  				"structuredToNull/nested/unseen"
  			],
  			oUpdatedValue = {
  				changed : "new",
  				unchanged : "same",
  				fromNull : "new",
  				toNull : null,
  				collection : [],
  				nested : {
  					changed : "new",
  					unchanged : "same",
  					fromNull : "new",
  					toNull : null
  				},
  				structuredFromNull : {
  					changed : "new",
  					toNull : null,
  					collection : [],
  					nested : {
  						changed : "new"
  					},
  					structuredToNull : null
  				},
  				structuredNull : null,
  				structuredToNull : null
  			};
  
  		function expectChange(sPath, vValue) {
  			oHelperMock.expects("fireChange")
  				.withExactArgs(sinon.match.same(mChangeListener), "base/path/" + sPath, vValue);
  		}
  
  		expectChange("changed", "new");
  		expectChange("fromNull", "new");
  		expectChange("toNull", null);
  		expectChange("nested/changed", "new");
  		expectChange("nested/fromNull", "new");
  		expectChange("nested/toNull", null);
  		expectChange("structuredFromNull/changed", "new");
  		expectChange("structuredFromNull/toNull", null);
  		expectChange("structuredFromNull/nested/changed", "new");
  		expectChange("structuredToNull", undefined); // side effect from _Helper.fireChanges
  		expectChange("structuredToNull/changed", undefined);
  		expectChange("structuredToNull/fromNull", undefined);
  		expectChange("structuredToNull/nested", undefined); // side effect from _Helper.fireChanges
  		expectChange("structuredToNull/nested/changed", undefined);
  
  		// code under test
  		_Helper.updateSelected(mChangeListener, "base/path", oOldValue, oNewValue, aSelect);
  
  		assert.deepEqual(oOldValue, oUpdatedValue);
  		assert.strictEqual(oOldValue.collection, oNewValue.collection);
  		assert.strictEqual(oOldValue.structuredFromNull.collection,
  			oNewValue.structuredFromNull.collection);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateSelected: no predicate", function (assert) {
  		var oCache = {foo : "bar"};
  
  		_Helper.updateSelected({}, "base/path", oCache, {foo : "baz"}, ["foo"]);
  
  		assert.deepEqual(oCache, {foo : "baz"});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateSelected: update all", function () {
  		var mChangeListeners = {},
  			oSource = {},
  			oTarget = {};
  
  		this.mock(_Helper).expects("updateAll").twice()
  			.withExactArgs(sinon.match.same(mChangeListeners), "base/path",
  				sinon.match.same(oTarget), sinon.match.same(oSource));
  
  		// code under test
  		_Helper.updateSelected(mChangeListeners, "base/path", oTarget, oSource);
  		_Helper.updateSelected(mChangeListeners, "base/path", oTarget, oSource,
  			["foo", "bar", "*", "baz"]);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateAll: properties", function (assert) {
  		var mChangeListeners = {},
  			oHelperMock = this.mock(_Helper),
  			oSource = {
  				"@$ui5._" : {
  					predicate : "('1')",
  					ignore : true
  				},
  				changed : "new",
  				fromNull : "new",
  				unchanged : "same"
  			},
  			oTarget = {
  				changed : "old",
  				fromNull : null,
  				unchanged : "same"
  			},
  			oUpdatedTarget = {
  				"@$ui5._" : {predicate : "('1')"},
  				changed : "new",
  				fromNull : "new",
  				unchanged : "same"
  			};
  
  		oHelperMock.expects("buildPath").withExactArgs("path", "@$ui5._");
  		oHelperMock.expects("buildPath").withExactArgs("path", "changed").returns("~changed");
  		oHelperMock.expects("buildPath").withExactArgs("path", "fromNull").returns("~fromNull");
  		oHelperMock.expects("buildPath").withExactArgs("path", "unchanged");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "~changed", "new");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "~fromNull", "new");
  
  		// code under test
  		_Helper.updateAll(mChangeListeners, "path", oTarget, oSource);
  
  		assert.deepEqual(oTarget, oUpdatedTarget);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateAll: recursion", function (assert) {
  		var aAdded = [],
  			mChangeListeners = {},
  			fnCheckKeyPredicate = "fnCheckKeyPredicate",
  			oHelperMock = this.mock(_Helper),
  			oSource = {
  				updated : {},
  				added : {}
  			},
  			oTarget = {
  				updated : {}
  			};
  
  		oHelperMock.expects("updateAll") // obviously :-)
  			.withExactArgs(sinon.match.same(mChangeListeners), "path", sinon.match.same(oTarget),
  				sinon.match.same(oSource), fnCheckKeyPredicate)
  			.callThrough();
  		oHelperMock.expects("buildPath").withExactArgs("path", "updated").returns("~updated");
  		oHelperMock.expects("updateAll")
  			.withExactArgs(sinon.match.same(mChangeListeners), "~updated",
  				sinon.match.same(oTarget.updated), sinon.match.same(oSource.updated),
  				fnCheckKeyPredicate);
  		oHelperMock.expects("buildPath").withExactArgs("path", "added").returns("~added");
  		oHelperMock.expects("updateAll")
  			.withExactArgs(sinon.match.same(mChangeListeners), "~added", {},
  				sinon.match.same(oSource.added), fnCheckKeyPredicate)
  			.returns(aAdded);
  
  		// code under test
  		_Helper.updateAll(mChangeListeners, "path", oTarget, oSource, fnCheckKeyPredicate);
  
  		assert.strictEqual(oTarget.added, aAdded);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateAll: source property is null", function (assert) {
  		var mChangeListeners = {},
  			oHelperMock = this.mock(_Helper),
  			oSource = {
  				changed : null,
  				unchanged : null,
  				structure : null
  			},
  			oTarget = {
  				changed : "old",
  				unchanged : null,
  				structure : {}
  			};
  
  		oHelperMock.expects("buildPath").withExactArgs("path", "changed").returns("~changed");
  		oHelperMock.expects("buildPath").withExactArgs("path", "unchanged");
  		oHelperMock.expects("buildPath").withExactArgs("path", "structure").returns("~structure");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "~changed", null);
  		oHelperMock.expects("fireChanges")
  			.withExactArgs(sinon.match.same(mChangeListeners), "~structure",
  				sinon.match.same(oTarget.structure), true);
  
  		// code under test
  		_Helper.updateAll(mChangeListeners, "path", oTarget, oSource);
  
  		assert.deepEqual(oTarget, oSource);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateAll: array", function (assert) {
  		var mChangeListeners = {},
  			oSource = {
  				array : []
  			},
  			oTarget = {};
  
  		this.mock(_Helper).expects("fireChange").never();
  
  		// code under test
  		_Helper.updateAll(mChangeListeners, "path", oTarget, oSource);
  
  		assert.strictEqual(oTarget.array, oSource.array);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateAll: add/change advertised action", function (assert) {
  		var mChangeListeners = {},
  			oHelperMock = this.mock(_Helper),
  			oSource = {
  				"#added" : {value : "new"},
  				"#changed" : {value : "new"}
  			},
  			oTarget = {
  				"#changed" : {value : "old"}
  			};
  
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/#added/value", "new");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/#changed/value", "new");
  
  		// code under test
  		_Helper.updateAll(mChangeListeners, "path", oTarget, oSource);
  
  		assert.deepEqual(oTarget, oSource);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateAll: check key predicate, unchanged", function (assert) {
  		var o = { // only a container for checkPredicate so that we can mock it
  				checkKeyPredicate : function () {}
  			},
  			oMock = this.mock(o),
  			oSource = {
  				"@$ui5._" : {
  					predicate : "('1')"
  				},
  				property : "new"
  			},
  			oTarget = {
  				"@$ui5._" : {
  					predicate : "('1')"
  				},
  				property : "old"
  			};
  
  		oMock.expects("checkKeyPredicate").withExactArgs("path").returns(true);
  
  		// code under test
  		_Helper.updateAll({}, "path", oTarget, oSource, o.checkKeyPredicate);
  
  		assert.deepEqual(oTarget, oSource);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("updateAll: check key predicate, changed", function (assert) {
  		var o = { // only a container for checkPredicate so that we can mock it
  				checkKeyPredicate : function () {}
  			},
  			oMock = this.mock(o),
  			oSource = {
  				"@$ui5._" : {
  					predicate : "('1')"
  				}
  			},
  			oTarget = {
  				"@$ui5._" : {
  					predicate : "('0')"
  				}
  			};
  
  		this.mock(_Helper).expects("buildPath").twice().withExactArgs("path", "@$ui5._");
  		oMock.expects("checkKeyPredicate").withExactArgs("path").returns(true);
  
  		assert.throws(function () {
  			// code under test
  			_Helper.updateAll({}, "path", oTarget, oSource, o.checkKeyPredicate);
  		}, new Error("Key predicate of 'path' changed from ('0') to ('1')"));
  
  		oMock.expects("checkKeyPredicate").withExactArgs("path").returns(false);
  
  		_Helper.updateAll({}, "path", oTarget, oSource, o.checkKeyPredicate);
  
  		assert.deepEqual(oTarget, oSource);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("informAll: comparison of old and new value fires all events", function (assert) {
  		var mChangeListeners = {},
  			oHelperMock = this.mock(_Helper),
  			oNew = {
  				add : "new",
  				changed : "changed",
  				deep : {
  					add : "new",
  					blank : {
  						add : "new"
  					},
  					changed : "changed",
  					unchanged : "same"
  				},
  				falsy : "",
  				getDeep : {
  					inside : "value"
  				},
  				getFlat : "now",
  				unchanged : "same"
  			},
  			oOld = {
  				changed : "init",
  				deep : {
  					changed : "init",
  					deepOld : {},
  					old : "old",
  					unchanged : "same"
  				},
  				falsy : null,
  				getDeep : "value",
  				getFlat : {
  					inside : "any"
  				},
  				old : "old",
  				unchanged : "same"
  			},
  			sUnchangedNew = JSON.stringify(oNew),
  			sUnchangedOld = JSON.stringify(oOld);
  
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/add", "new");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/changed", "changed");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/deep/add", "new");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/deep/blank/add", "new");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/deep/changed", "changed");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/deep/deepOld", null);
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/deep/old", null);
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/getDeep/inside", "value");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/falsy", "");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/getFlat", "now");
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/getFlat/inside", null);
  		oHelperMock.expects("fireChange")
  			.withExactArgs(sinon.match.same(mChangeListeners), "path/old", null);
  
  		// code under test
  		_Helper.informAll(mChangeListeners, "path", oOld, oNew);
  
  		// assertion - no change on the old and the new value
  		assert.strictEqual(JSON.stringify(oOld), sUnchangedOld);
  		assert.strictEqual(JSON.stringify(oNew), sUnchangedNew);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("makeAbsolute", function (assert) {
  		assert.strictEqual(_Helper.makeAbsolute("/foo/bar", "/baz"), "/foo/bar");
  		assert.strictEqual(_Helper.makeAbsolute("baz", "/foo/bar"), "/foo/baz");
  		assert.strictEqual(_Helper.makeAbsolute("Foo('1')/Bar(baz='2',qux=3)", "/service/"),
  			"/service/Foo('1')/Bar(baz='2',qux=3)");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("drillDown", function (assert) {
  		var oObject = {
  				"foo" : "bar",
  				"bar" : {
  					"baz" : "qux"
  				},
  				"null" : null
  			};
  
  		assert.strictEqual(_Helper.drillDown(oObject, []), oObject);
  		assert.strictEqual(_Helper.drillDown(oObject, ["foo"]), "bar");
  		assert.strictEqual(_Helper.drillDown(oObject, ["bar", "baz"]), "qux");
  		assert.strictEqual(_Helper.drillDown(oObject, ["unknown"]), undefined);
  		assert.strictEqual(_Helper.drillDown(oObject, ["unknown", "value"]), undefined);
  		assert.strictEqual(_Helper.drillDown(oObject, ["null"]), null);
  		assert.strictEqual(_Helper.drillDown(oObject, ["null", "value"]), undefined);
  	});
  
  	//*********************************************************************************************
  	[{
  		dataPath : "/Foo",
  		metaPath : "/Foo"
  	}, { // e.g. function call plus key predicate
  		dataPath : "/Foo/name.space.bar_42(key='value')(key='value')",
  		metaPath : "/Foo/name.space.bar_42"
  	}, {
  		dataPath : "/Foo(key='value')(key='value')/bar",
  		metaPath : "/Foo/bar"
  	}, { // any segment with digits only
  		dataPath : "/Foo/" + Date.now(),
  		metaPath : "/Foo"
  	}, {
  		dataPath : "/Foo/" + Date.now() + "/bar",
  		metaPath : "/Foo/bar"
  	}, { // global removal needed
  		dataPath : "/Foo(key='value')/" + Date.now() + "/bar(key='value')/"  + Date.now(),
  		metaPath : "/Foo/bar"
  	}, { // transient entity
  		dataPath : "/Foo($uid=id-1-23)/bar",
  		metaPath : "/Foo/bar"
  	}, { // empty path
  		dataPath : "",
  		metaPath : ""
  	}, { // relative with key predicate
  		dataPath : "Foo('42')",
  		metaPath : "Foo"
  	}, { // relative, only key predicate
  		dataPath : "('42')",
  		metaPath : ""
  	}, { // relative, leading key predicate
  		dataPath : "('42')/bar",
  		metaPath : "bar"
  	}, { // relative, only index
  		dataPath : "42",
  		metaPath : ""
  	}, { // relative, leading index
  		dataPath : "42/bar",
  		metaPath : "bar"
  	}].forEach(function (oFixture) {
  		QUnit.test("getMetaPath: " + oFixture.dataPath, function (assert) {
  			var sMetaPath = _Helper.getMetaPath(oFixture.dataPath);
  
  			assert.strictEqual(sMetaPath, oFixture.metaPath);
  		});
  	});
  	//TODO $all, $count, $crossjoin, $ref, $value
  	// Q: Do we need to keep signatures to resolve overloads?
  	// A: Most probably not. The spec says "All bound functions with the same function name and
  	//    binding parameter type within a namespace MUST specify the same return type."
  	//    "All unbound functions with the same function name within a namespace MUST specify the
  	//    same return type." --> We can find the return type (from the binding parameter type).
  	//    If it comes to annotations, the signature might make a difference, but then "unordered
  	//    set of (non-binding) parameter names" is unique.
  
  	//*********************************************************************************************
  	QUnit.test("clone", function (assert) {
  		var vClone = {},
  			oJSONMock = this.mock(JSON),
  			fnReplacer = function () {},
  			sStringified = "{}",
  			vValue = {};
  
  		// code under test
  		assert.strictEqual(_Helper.clone(null), null);
  
  		oJSONMock.expects("stringify").withExactArgs(sinon.match.same(vValue), undefined)
  			.returns(sStringified);
  		oJSONMock.expects("parse").withExactArgs(sStringified).returns(vClone);
  
  		// code under test
  		assert.strictEqual(_Helper.clone(vValue), vClone);
  
  		oJSONMock.expects("stringify")
  			.withExactArgs(sinon.match.same(vValue), sinon.match.same(fnReplacer))
  			.returns(sStringified);
  		oJSONMock.expects("parse").withExactArgs(sStringified).returns(vClone);
  
  		// code under test
  		assert.strictEqual(_Helper.clone(vValue, fnReplacer), vClone);
  
  		// code under test
  		assert.strictEqual(_Helper.clone(undefined), undefined);
  
  		// code under test
  		assert.ok(isNaN(_Helper.clone(NaN)));
  
  		// code under test
  		assert.strictEqual(_Helper.clone(Infinity), Infinity);
  
  		// code under test
  		assert.strictEqual(_Helper.clone(-Infinity), -Infinity);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getPrivateAnnotation", function (assert) {
  		var oObject = {
  				"@$ui5._" : {
  					"transient" : "foo"
  				}
  			};
  
  		assert.strictEqual(_Helper.getPrivateAnnotation({}, "foo"), undefined);
  		assert.strictEqual(_Helper.getPrivateAnnotation(oObject, "foo"), undefined);
  		assert.strictEqual(_Helper.getPrivateAnnotation(oObject, "transient"), "foo");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("hasPrivateAnnotation", function (assert) {
  		var oObject = {
  				"@$ui5._" : {
  					"transient" : undefined
  				}
  			};
  
  		assert.strictEqual(_Helper.hasPrivateAnnotation({}, "foo"), false);
  		assert.strictEqual(_Helper.hasPrivateAnnotation(oObject, "foo"), false);
  		assert.strictEqual(_Helper.hasPrivateAnnotation(oObject, "transient"), true);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("setPrivateAnnotation", function (assert) {
  		var oObject = {};
  
  		// code under test
  		_Helper.setPrivateAnnotation(oObject, "transient", "foo");
  
  		assert.strictEqual(_Helper.getPrivateAnnotation(oObject, "transient"), "foo");
  
  		// code under test
  		_Helper.setPrivateAnnotation(oObject, "transient", "bar");
  
  		assert.strictEqual(_Helper.getPrivateAnnotation(oObject, "transient"), "bar");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("deletePrivateAnnotation", function (assert) {
  		var oObject = {};
  
  		// code under test
  		_Helper.deletePrivateAnnotation(oObject, "transient");
  
  		// code under test
  		_Helper.setPrivateAnnotation(oObject, "transient", undefined);
  
  		assert.strictEqual(_Helper.hasPrivateAnnotation(oObject, "transient"), true);
  		assert.strictEqual(_Helper.getPrivateAnnotation(oObject, "transient"), undefined);
  
  		// code under test
  		_Helper.deletePrivateAnnotation(oObject, "transient");
  
  		assert.strictEqual(_Helper.hasPrivateAnnotation(oObject, "transient"), false);
  		assert.strictEqual(_Helper.getPrivateAnnotation(oObject, "transient"), undefined);
  	});
  
  	//*********************************************************************************************
  [false, true].forEach(function (bClientSide) {
  	QUnit.test("publicClone: bClientSide=" + bClientSide, function (assert) {
  		var oCloneMock = this.mock(_Helper).expects("clone")
  				.withExactArgs("~value~", sinon.match.func),
  			fnReplacer;
  
  		oCloneMock.returns("~clone~");
  
  		// code under test
  		assert.strictEqual(_Helper.publicClone("~value~", bClientSide), "~clone~");
  
  		fnReplacer = oCloneMock.getCall(0).args[1];
  
  		// Check:
  		//   - sKey === "@$ui5._" => fnReplacer(sKey, vValue) === undefined for each vValue
  		//   - sKey !== "@$ui5._" => fnReplacer(sKey, vValue) === vValue for each vValue
  		// code under test
  		assert.strictEqual(fnReplacer("@$ui5._", 42), undefined);
  		assert.strictEqual(fnReplacer("@$ui5.node.level", 2), bClientSide ? undefined : 2);
  		assert.strictEqual(fnReplacer("@$ui5.transient", true), bClientSide ? undefined : true);
  		assert.strictEqual(fnReplacer("ui5._", "bar"), "bar");
  	});
  });
  
  	//*********************************************************************************************
  	[{
  		mHeaders : {},
  		mResolvedHeader : {}
  	}, {
  		mHeaders : {"If-Match" : "foo"},
  		mResolvedHeader : {"If-Match" : "foo"}
  	}, {
  		mHeaders : undefined,
  		mResolvedHeader : undefined
  	}, {
  		mHeaders : {"If-Match" : null},
  		mResolvedHeader : {"If-Match" : null}
  	}].forEach(function (oFixture, i) {
  		QUnit.test("resolveIfMatchHeader: no clone - " + i, function (assert) {
  			var mResolvedHeaders;
  
  			// code under test
  			mResolvedHeaders = _Helper.resolveIfMatchHeader(oFixture.mHeaders);
  
  			assert.strictEqual(mResolvedHeaders, oFixture.mHeaders);
  			assert.deepEqual(mResolvedHeaders, oFixture.mResolvedHeader);
  		});
  	});
  
  	//*********************************************************************************************
  	[{
  		mHeaders : {"If-Match" : {}},
  		mResolvedHeader : {}
  	}, {
  		mHeaders : {"If-Match" : {"@odata.etag" : "foo"}},
  		mResolvedHeader : {"If-Match" : "foo"}
  	}, {
  		mHeaders : {"If-Match" : {"@odata.etag" : ""}},
  		mResolvedHeader : {"If-Match" : ""}
  	}].forEach(function (oFixture, i) {
  		QUnit.test("resolveIfMatchHeader: copy on write - " + i, function (assert) {
  			var mResolvedHeaders;
  
  			// code under test
  			mResolvedHeaders = _Helper.resolveIfMatchHeader(oFixture.mHeaders);
  
  			assert.notStrictEqual(mResolvedHeaders, oFixture.mHeaders);
  			assert.deepEqual(mResolvedHeaders, oFixture.mResolvedHeader);
  		});
  	});
  
  	//*********************************************************************************************
  	[{
  		sTransientPredicate : "($uid=1)",
  		mExpectedMap : {
  			"('23')/Team_Id" : ["listener0"],
  			"('23')/Name" : ["listener1"],
  			"('23')/TEAM_2_EMPLOYEES($uid=2)/EMPLOYEE_2_EQUIPMENTS($uid=4)/ID" : ["listener2"],
  			"('23')/TEAM_2_EMPLOYEES($uid=2)/EMPLOYEE_2_EQUIPMENTS($uid=4)/Category"
  				: ["listener3"],
  			"('23')/TEAM_2_EMPLOYEES($uid=2)/ID" : ["listener4"],
  			"('23')/TEAM_2_EMPLOYEES('47')/ID" : ["listener5"],
  			"('42')/Team_Id" : ["listener6"],
  			"('42')/Name" : ["listener7"],
  			"('42')/TEAM_2_EMPLOYEES($uid=3)/ID" : ["listener8"]
  		}
  	}, {
  		sTransientPredicate : "($uid=2)",
  		mExpectedMap : {
  			"($uid=1)/Team_Id" : ["listener0"],
  			"($uid=1)/Name" : ["listener1"],
  			"($uid=1)/TEAM_2_EMPLOYEES('23')/EMPLOYEE_2_EQUIPMENTS($uid=4)/ID" : ["listener2"],
  			"($uid=1)/TEAM_2_EMPLOYEES('23')/EMPLOYEE_2_EQUIPMENTS($uid=4)/Category"
  				: ["listener3"],
  			"($uid=1)/TEAM_2_EMPLOYEES('23')/ID" : ["listener4"],
  			"($uid=1)/TEAM_2_EMPLOYEES('47')/ID" : ["listener5"],
  			"('42')/Team_Id" : ["listener6"],
  			"('42')/Name" : ["listener7"],
  			"('42')/TEAM_2_EMPLOYEES($uid=3)/ID" : ["listener8"]
  		}
  	}].forEach(function (oFixture) {
  		var sTitle = "update transient path, transient predicate: " + oFixture.sTransientPredicate;
  
  		QUnit.test(sTitle, function (assert) {
  			var mMap = {
  					"($uid=1)/Team_Id" : ["listener0"],
  					"($uid=1)/Name" : ["listener1"],
  					"($uid=1)/TEAM_2_EMPLOYEES($uid=2)/EMPLOYEE_2_EQUIPMENTS($uid=4)/ID"
  						: ["listener2"],
  					"($uid=1)/TEAM_2_EMPLOYEES($uid=2)/EMPLOYEE_2_EQUIPMENTS($uid=4)/Category"
  						: ["listener3"],
  					"($uid=1)/TEAM_2_EMPLOYEES($uid=2)/ID" : ["listener4"],
  					"($uid=1)/TEAM_2_EMPLOYEES('47')/ID" : ["listener5"],
  					"('42')/Team_Id" : ["listener6"],
  					"('42')/Name" : ["listener7"],
  					"('42')/TEAM_2_EMPLOYEES($uid=3)/ID" : ["listener8"]
  				};
  
  			// code under test
  			_Helper.updateTransientPaths(mMap, oFixture.sTransientPredicate, "('23')");
  
  			assert.deepEqual(mMap, oFixture.mExpectedMap);
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("addByPath", function (assert) {
  		var mMap = {};
  
  		_Helper.addByPath(mMap, "path1", "item1");
  		assert.deepEqual(mMap, {"path1" : ["item1"]});
  
  		_Helper.addByPath(mMap, "path2", "item2");
  		assert.deepEqual(mMap, {"path1" : ["item1"], "path2" : ["item2"]});
  
  		_Helper.addByPath(mMap, "path3", undefined);
  		assert.deepEqual(mMap, {"path1" : ["item1"], "path2" : ["item2"]});
  
  		_Helper.addByPath(mMap, "path1", "item3");
  		assert.deepEqual(mMap, {"path1" : ["item1", "item3"], "path2" : ["item2"]});
  
  		_Helper.addByPath(mMap, "path2", "item2");
  		assert.deepEqual(mMap, {"path1" : ["item1", "item3"], "path2" : ["item2"]});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("removeByPath", function (assert) {
  		var mMap = {"path1": ["item1", "item2"]};
  
  		_Helper.removeByPath(mMap, "path1", "item2");
  		assert.deepEqual(mMap, {"path1" : ["item1"]});
  
  		_Helper.removeByPath(mMap, "path2", "item2");
  		assert.deepEqual(mMap, {"path1" : ["item1"]});
  
  		_Helper.removeByPath(mMap, "path1", "item2");
  		assert.deepEqual(mMap, {"path1" : ["item1"]});
  
  		_Helper.removeByPath(mMap, "path1", "item1");
  		assert.deepEqual(mMap, {});
  	});
  
  	//*********************************************************************************************
  	[{
  		aChildren : [],
  		aAncestors : [],
  		mChildren : {}
  	}, {
  		aChildren : ["A"],
  		aAncestors : ["B"],
  		mChildren : {}
  	}, {
  		aChildren : ["A", "B"],
  		aAncestors : ["C", "A"],
  		mChildren : {"A" : true}
  	}, {
  		aChildren : ["Address/City"],
  		aAncestors : ["Address"],
  		mChildren : {"Address/City" : true}
  	}, {
  		aChildren : ["Address/City/Block"],
  		aAncestors : ["Address"],
  		mChildren : {"Address/City/Block" : true}
  	}, {
  		aChildren : ["Address/City/Block"],
  		aAncestors : ["Address/City"],
  		mChildren : {"Address/City/Block" : true}
  	}].forEach(function (o, i) {
  		QUnit.test("addChildrenWithAncestor: " + i, function (assert) {
  			var sAncestors = JSON.stringify(o.aAncestors),
  				mChildren = {},
  				sChildren = JSON.stringify(o.aChildren);
  
  			// code under test
  			assert.strictEqual(
  				_Helper.addChildrenWithAncestor(o.aChildren, o.aAncestors, mChildren),
  				undefined, "no return value");
  
  			assert.deepEqual(mChildren, o.mChildren);
  			assert.strictEqual(JSON.stringify(o.aAncestors), sAncestors, "children unmodified");
  			assert.strictEqual(JSON.stringify(o.aChildren), sChildren, "ancestors unmodified");
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("addChildrenWithAncestor: no ancestors", function (assert) {
  		var aAncestors = [],
  			mChildren = {};
  
  		this.mock(aAncestors).expects("indexOf").never(); // no need to ask :-)
  
  		// code under test
  		_Helper.addChildrenWithAncestor(["A"], aAncestors, mChildren);
  
  		assert.deepEqual(mChildren, {});
  	});
  
  	//*********************************************************************************************
  [
  	{$kind : "Entity"},
  	{$kind : "NavigationProperty"},
  	{$kind : "NavigationProperty", $isCollection : true}
  ].forEach(function (oRootMetadata, i) {
  	QUnit.test("intersectQueryOptions: real intersection", function (assert) {
  		var aCacheSelects = [/*"A", "B/b", "C", ...*/],
  			mCacheQueryOptions = {
  				$expand : {"to1" : null},
  				$select : aCacheSelects,
  				"sap-client" : "123"
  			},
  			sCacheQueryOptions = JSON.stringify(mCacheQueryOptions),
  			mChildren,
  			mExpectedResult = {
  				$expand : {to1 : null},
  				$select: ["A/a", "B/b"],
  				"sap-client" : "123"
  			},
  			oType = {},
  			fnFetchMetadata = getFetchMetadata({
  				"/Me" : oRootMetadata,
  				"/Me/" : oType,
  				"/Me/A/a" : "",
  				"/Me/B/b" : "",
  				"/Me/to1" : "1"
  			}),
  			oHelperMock = this.mock(_Helper),
  			mNavigationPropertyPaths = {},
  			aPaths = [/*"to1", "A/a", "B"*/];
  
  		oHelperMock.expects("addChildrenWithAncestor")
  			.withExactArgs(sinon.match.same(aPaths), sinon.match.same(aCacheSelects), {})
  			.callsFake(function (_aChildren, _aAncestors, mChildren0) {
  				mChildren = mChildren0;
  				mChildren["A/a"] = true;
  			});
  		oHelperMock.expects("addChildrenWithAncestor")
  			.withExactArgs(sinon.match.same(aCacheSelects), sinon.match.same(aPaths),
  				sinon.match.object)
  			.callsFake(function (_aChildren, _aAncestors, mChildren0) {
  				assert.strictEqual(mChildren0, mChildren);
  				mChildren["B/b"] = true;
  			});
  		oHelperMock.expects("addChildrenWithAncestor")
  			.withExactArgs(["to1"], sinon.match.same(aPaths), {})
  			.callsFake(function (_aChildren, _aAncestors, mSet) {
  				mSet.to1 = true;
  			});
  		oHelperMock.expects("selectKeyProperties").exactly(i % 2)
  			.withExactArgs(mExpectedResult, sinon.match.same(oType));
  
  		assert.deepEqual(
  			// code under test
  			_Helper.intersectQueryOptions(mCacheQueryOptions, aPaths, fnFetchMetadata, "/Me",
  				mNavigationPropertyPaths),
  			mExpectedResult);
  
  		assert.strictEqual(JSON.stringify(mCacheQueryOptions), sCacheQueryOptions, "unmodified");
  		assert.deepEqual(mNavigationPropertyPaths, {});
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("intersectQueryOptions: no query options", function (assert) {
  		// code under test
  		assert.strictEqual(_Helper.intersectQueryOptions(undefined, ["*"]), null);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("intersectQueryOptions: no $select", function (assert) {
  		// code under test
  		assert.strictEqual(_Helper.intersectQueryOptions({}, ["*"]), null);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("intersectQueryOptions: empty $select", function (assert) {
  		// code under test
  		assert.strictEqual(_Helper.intersectQueryOptions({$select : []}, ["B"]), null);
  	});
  
  	//*********************************************************************************************
  	[{
  		$expand : {
  			"toN" : null
  		},
  		aPaths : ["toN/a"],
  		sNavigationProperty : "/Me/toN"
  	}].forEach(function (oFixture) {
  		var sMessage = "Unsupported collection-valued navigation property "
  			+ oFixture.sNavigationProperty;
  
  		QUnit.test("intersectQueryOptions: " + sMessage, function (assert) {
  			var mCacheQueryOptions = {
  					$expand : oFixture.$expand,
  					$select : ["A"]
  				},
  				sCacheQueryOptions = JSON.stringify(mCacheQueryOptions),
  				mMetaPath2Type = {};
  
  			mMetaPath2Type[oFixture.sNavigationProperty] = "N";
  			assert.throws(function () {
  				// code under test
  				_Helper.intersectQueryOptions(mCacheQueryOptions, oFixture.aPaths,
  					getFetchMetadata(mMetaPath2Type), "/Me", {});
  				// Note: this error might be used by the caller to resort to other measures...
  			}, new Error(sMessage));
  
  			assert.strictEqual(JSON.stringify(mCacheQueryOptions), sCacheQueryOptions,
  				"unmodified");
  		});
  	});
  
  	//*********************************************************************************************
  	[{
  		sPrefix : undefined,
  		mExpectedNavigationPropertyPaths : {"A/toN" : true, "toN" : true}
  	}, {
  		sPrefix : "~",
  		mExpectedNavigationPropertyPaths : {"~/A/toN" : true, "~/toN" : true}
  	}].forEach(function (oFixture) {
  		var sTitle = "intersectQueryOptions: collection-valued navigation property"
  				+ "; prefix = " + oFixture.sPrefix;
  
  		QUnit.test(sTitle, function (assert) {
  			var mNavigationPropertyPaths = {}, // a map w/o values is a set ;-)
  				mCacheQueryOptions = {
  					$expand : {
  						"A/toN" : null,
  						"toN" : null
  					},
  					$select : []
  				},
  				sCacheQueryOptions = JSON.stringify(mCacheQueryOptions),
  				mExpectedResult = {$expand : {"A/toN" : null, "toN" : null}, $select : ["A/toN"]},
  				oType = {},
  				fnFetchMetadata = getFetchMetadata({
  					"/Me" : {},
  					"/Me/" : oType,
  					"/Me/A" : "",
  					"/Me/A/toN" : "N",
  					"/Me/toN" : "N"
  				});
  
  			// code under test
  			assert.deepEqual(
  				_Helper.intersectQueryOptions(mCacheQueryOptions, ["A", "toN"],
  					fnFetchMetadata, "/Me", mNavigationPropertyPaths, oFixture.sPrefix),
  				mExpectedResult
  			);
  
  			assert.deepEqual(mNavigationPropertyPaths, oFixture.mExpectedNavigationPropertyPaths);
  			assert.strictEqual(JSON.stringify(mCacheQueryOptions), sCacheQueryOptions,
  				"unmodified");
  		});
  	});
  
  	//*********************************************************************************************
  	[{
  		aPaths : [],
  		mResult : null // nothing to do
  	}, {
  		aPaths : ["X", "Y", "Z"],
  		mResult : null // nothing to do
  	}, {
  		aPaths : ["A/to1"],
  		mResult : null // nothing to do, only navigation property has changed, but it is unused
  	}, {
  		aPaths : ["A/toN"],
  		mResult : null // nothing to do, only navigation property has changed, but it is unused
  	}, {
  		aPaths : ["A/to1/D", "B/C/toN", "X"],
  		mResult : null // nothing to do, only navigation property has changed, but it is unused
  	}, {
  		aPaths : ["A/C"],
  		mResult : {
  			$select : ["A/C"], // only structural properties
  			"sap-client" : "123"
  		}
  	}, {
  		aPaths : ["A", "toC"],
  		mResult : {
  			$expand : {
  				"toC" : null
  			},
  			$select : ["A"],
  			"sap-client" : "123"
  		}
  	}, {
  		aPaths : ["toA", "toC"], // toB omitted
  		mResult : {
  			$expand : {
  				"toA" : null,
  				"toC" : null
  			},
  			$select : ["toA"], // avoid $select= in URL, use any navigation property
  			"sap-client" : "123"
  		}
  	}, {
  		aPaths : ["toC", "toB", "toB/b", "D"], // Note "toB" is stronger than "toB/b"!
  		mResult : {
  			$expand : {
  				"toB" : {$select : ["a"]},
  				"toC" : null,
  				"D/E/toD" : {$select : ["d"]}
  			},
  			$select : ["toB"], // avoid $select= in URL, use any navigation property
  			"sap-client" : "123"
  		}
  	}, {
  		aPaths : ["toA/a", "toB/z"],
  		mResult : {
  			$expand : {
  				"toA" : {
  					$select : ["a"]
  				}
  			},
  			$select : ["toA"], // avoid $select= in URL, use any navigation property
  			"sap-client" : "123"
  		},
  		mSelectKeyProperties : {
  			"/Me/toA/" : {$select : ["a"]}
  		}
  	}, {
  		aPaths : ["A", "toA/a"],
  		mResult : {
  			$expand : {
  				"toA" : {
  					$select : ["a"]
  				}
  			},
  			$select : ["A"],
  			"sap-client" : "123"
  		},
  		mSelectKeyProperties : {
  			"/Me/toA/" : {$select : ["a"]}
  		}
  	}, {
  		aPaths : ["toE/e", "toG/g"],
  		mResult : {
  			$expand : {
  				"toE" : {
  					$select : ["e"]
  				},
  				"toG" : {
  					$select : ["g"]
  				}
  			},
  			$select : ["toE"], // avoid $select= in URL, use any navigation property
  			"sap-client" : "123"
  		},
  		mSelectKeyProperties : {
  			"/Me/toE/" : {$select : ["e"]},
  			"/Me/toG/" : {$select : ["g"]}
  		}
  //	}, { //TODO who would need this?
  //		aPaths : ["E"],
  //		mResult : {
  //			$select : ["E/toF"],
  //			"sap-client" : "123"
  //		}
  	}, {
  		aPaths : ["X", "*", "Z"],
  		mResult : {
  			$select : ["A", "B", "C", "E/toF"],
  			"sap-client" : "123"
  		}
  	}].forEach(function (o, i) {
  		var sTitle = "intersectQueryOptions: " + o.aPaths + ", " + i;
  
  		QUnit.test(sTitle, function (assert) {
  			var mCacheQueryOptions = {
  					$expand : {
  						"toA" : null,
  						"toB" : {$select : ["a"]},
  						"toC" : null,
  						"D/E/toD" : {$select : ["d"]},
  						"toE" : {},
  						"toG" : true
  					},
  					$select : ["A", "B", "C", "E/toF"],
  					"sap-client" : "123"
  				},
  				sCacheQueryOptions = JSON.stringify(mCacheQueryOptions),
  				mMetaPath2Type = {
  					"/Me" : {},
  					"/Me/" : {},
  					"/Me/A" : "",
  					"/Me/A/C" : "",
  					"/Me/A/to1" : "1",
  					"/Me/A/toN" : "N",
  					"/Me/B/C/toN" : "N",
  					"/Me/B/C" : "",
  					"/Me/D/E/toD" : "1",
  					"/Me/E/toF" : "1",
  					"/Me/toA" : "1",
  					"/Me/toA/" : {},
  					"/Me/toA/a" : "",
  					"/Me/toB" : "1",
  					"/Me/toC" : "1",
  					"/Me/toE" : "1",
  					"/Me/toE/" : {},
  					"/Me/toE/e" : "",
  					"/Me/toG" : "1",
  					"/Me/toG/" : {},
  					"/Me/toG/g" : ""
  				},
  				fnFetchMetadata = getFetchMetadata(mMetaPath2Type),
  				oHelperMock = this.mock(_Helper),
  				mNavigationPropertyPaths = {};
  
  			if (o.mSelectKeyProperties) {
  				Object.keys(o.mSelectKeyProperties).forEach(function (sEntityPath) {
  					oHelperMock.expects("selectKeyProperties")
  						.withExactArgs(o.mSelectKeyProperties[sEntityPath],
  							sinon.match.same(mMetaPath2Type[sEntityPath]));
  				});
  			}
  			assert.deepEqual(
  				// code under test
  				_Helper.intersectQueryOptions(mCacheQueryOptions, o.aPaths, fnFetchMetadata, "/Me",
  					mNavigationPropertyPaths),
  				o.mResult);
  
  			assert.strictEqual(JSON.stringify(mCacheQueryOptions), sCacheQueryOptions,
  				"unmodified");
  			assert.deepEqual(mNavigationPropertyPaths, {});
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("intersectQueryOptions with empty $select allowed", function (assert) {
  		var mCacheQueryOptions = {
  				$expand : {
  					"toA" : null
  				},
  				$select : ["A"],
  				"sap-client" : "123"
  			},
  			sCacheQueryOptions = JSON.stringify(mCacheQueryOptions),
  			fnFetchMetadata = getFetchMetadata({
  				"/Me" : {},
  				"/Me/toA" : "1"
  			}),
  			mNavigationPropertyPaths = {};
  
  		assert.deepEqual(
  			// code under test
  			_Helper.intersectQueryOptions(mCacheQueryOptions, ["toA"], fnFetchMetadata, "/Me",
  				mNavigationPropertyPaths, "", true),
  			{
  				$expand : {
  					"toA" : null
  				},
  				$select : [],
  				"sap-client" : "123"
  			});
  
  		assert.strictEqual(JSON.stringify(mCacheQueryOptions), sCacheQueryOptions,
  			"unmodified");
  		assert.deepEqual(mNavigationPropertyPaths, {});
  	});
  
  	//*********************************************************************************************
  	[false, true].forEach(function (bPrefix) {
  		var sPrefix = bPrefix ? "~" : undefined;
  
  		QUnit.test("intersectQueryOptions: recursion; prefix = " + sPrefix, function (assert) {
  			var mCacheQueryOptions = {
  					$expand : {
  						"toC" : null,
  						"D/E/toD" : {$select : ["d"]}
  					},
  					$select : [],
  					"sap-client" : "123"
  				},
  				mCacheQueryOptions0 = {},
  				mCacheQueryOptions1 = {},
  				mExpectedResult = {
  					$expand : {
  						"toC" : mCacheQueryOptions0,
  						"D/E/toD" : mCacheQueryOptions1
  					},
  					$select : ["toC"],
  					"sap-client" : "123"
  				},
  				oType = {},
  				fnFetchMetadata = getFetchMetadata({
  					"/Me" : {},
  					"/Me/" : oType,
  					"/Me/D/E/toD" : "1",
  					"/Me/toC" : "1"
  				}),
  				oHelperMock = this.mock(_Helper),
  				mNavigationPropertyPaths = {},
  				aPaths = ["toC/d", "D/E/toD/d"],
  				aStrippedPaths0 = ["d"],
  				aStrippedPaths1 = ["d"];
  
  			oHelperMock.expects("intersectQueryOptions")
  				.withExactArgs(sinon.match.same(mCacheQueryOptions),
  					sinon.match.same(aPaths), sinon.match.same(fnFetchMetadata), "/Me",
  					sinon.match.same(mNavigationPropertyPaths), sPrefix)
  				.callThrough(); // for code under test
  			// intersect for $select tested already above - no sinon.match.same tests
  			oHelperMock.expects("addChildrenWithAncestor").withExactArgs(aPaths, [], {});
  			oHelperMock.expects("addChildrenWithAncestor").withExactArgs([], aPaths, {});
  			// first navigation property
  			oHelperMock.expects("addChildrenWithAncestor")
  				.withExactArgs(["toC"], sinon.match.same(aPaths), {});
  			oHelperMock.expects("stripPathPrefix").withExactArgs("toC", sinon.match.same(aPaths))
  				.returns(aStrippedPaths0);
  			oHelperMock.expects("intersectQueryOptions")
  				.withExactArgs({}, sinon.match.same(aStrippedPaths0),
  					sinon.match.same(fnFetchMetadata), "/Me/toC",
  					sinon.match.same(mNavigationPropertyPaths), bPrefix ? "~/toC" : "toC")
  				.returns(mCacheQueryOptions0);
  			// second navigation property
  			oHelperMock.expects("addChildrenWithAncestor")
  				.withExactArgs(["D/E/toD"], sinon.match.same(aPaths), {});
  			oHelperMock.expects("stripPathPrefix")
  				.withExactArgs("D/E/toD", sinon.match.same(aPaths))
  				.returns(aStrippedPaths1);
  			oHelperMock.expects("intersectQueryOptions")
  				.withExactArgs(sinon.match.same(mCacheQueryOptions.$expand["D/E/toD"]),
  					sinon.match.same(aStrippedPaths1), sinon.match.same(fnFetchMetadata),
  					"/Me/D/E/toD", sinon.match.same(mNavigationPropertyPaths),
  					bPrefix ? "~/D/E/toD" : "D/E/toD")
  				.returns(mCacheQueryOptions1);
  
  			assert.deepEqual(
  				// code under test
  				_Helper.intersectQueryOptions(mCacheQueryOptions, aPaths, fnFetchMetadata, "/Me",
  					mNavigationPropertyPaths, sPrefix),
  				mExpectedResult);
  			assert.deepEqual(mNavigationPropertyPaths, {});
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("intersectQueryOptions: empty navigation property path", function (assert) {
  		// Note: this is just self-guarding, the method must not be used in this case
  		assert.throws(function () {
  			// code under test
  			_Helper.intersectQueryOptions({}, [""]);
  		}, new Error("Unsupported empty navigation property path"));
  	});
  
  	//*********************************************************************************************
  	QUnit.test("intersectQueryOptions: * inside $select", function (assert) {
  		this.mock(_Helper);
  		var  mCacheQueryOptions = {
  				$expand : {"n/a" : null},
  				$select : ["A", "*", "Z"],
  				"sap-client" : "123"
  			},
  			oType = {},
  			fnFetchMetadata = getFetchMetadata({
  				"/Me" : {},
  				"/Me/" : oType,
  				"/Me/B" : "",
  				"/Me/B/C" : "",
  				"/Me/B/toN" : "N",
  				"/Me/D" : "",
  				"/Me/toN" : "N"
  			});
  
  		function test(mExpectedResult) {
  			var sCacheQueryOptions = JSON.stringify(mCacheQueryOptions),
  				mNavigationPropertyPaths = {};
  
  			// code under test
  			assert.deepEqual(
  				_Helper.intersectQueryOptions(mCacheQueryOptions, ["B/C", "D", "B/toN", "toN"],
  					fnFetchMetadata, "/Me", mNavigationPropertyPaths),
  				mExpectedResult);
  
  			assert.strictEqual(JSON.stringify(mCacheQueryOptions), sCacheQueryOptions,
  				"unmodified");
  			assert.deepEqual(mNavigationPropertyPaths, {});
  		}
  
  		test({$select : ["B/C", "D"], "sap-client" : "123"});
  		delete mCacheQueryOptions.$select; // missing $select means *
  		test({$select : ["B/C", "D"], "sap-client" : "123"});
  		mCacheQueryOptions = undefined; // query options are optional
  		test({$select : ["B/C", "D"]});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("stripPathPrefix", function (assert) {
  		var aResult,
  			aPaths = [];
  
  		// code under test
  		aResult = _Helper.stripPathPrefix("A", aPaths);
  
  		assert.deepEqual(aResult, []);
  		assert.notStrictEqual(aResult, aPaths);
  
  		// code under test
  		assert.deepEqual(_Helper.stripPathPrefix("A", ["A"]), [""]);
  
  		// code under test
  		assert.deepEqual(_Helper.stripPathPrefix("", ["A", "B"]), ["A", "B"]);
  
  		aPaths = ["Z", "A/B", "AA", "A/C/D/C"];
  
  		// code under test
  		aResult = _Helper.stripPathPrefix("A", aPaths);
  
  		assert.deepEqual(aResult, ["B", "C/D/C"]);
  		assert.deepEqual(aPaths, ["Z", "A/B", "AA", "A/C/D/C"]);
  	});
  
  	//*********************************************************************************************
  	[{
  		before : [],
  		merge : ["foo"],
  		after : ["foo"]
  	}, {
  		before : ["foo"],
  		merge : ["bar"],
  		after : ["foo", "bar"]
  	}, {
  		before : ["foo", "bar"],
  		merge : ["bar", "baz"],
  		after : ["foo", "bar", "baz"]
  	}, {
  		before : undefined,
  		merge : ["foo", "bar"],
  		after : ["foo", "bar"]
  	}].forEach(function (oFixture) {
  		QUnit.test("addToSelect", function (assert) {
  			var mQueryOptions = {$foo : "bar", $select : oFixture.before};
  
  			// code under test
  			_Helper.addToSelect(mQueryOptions, oFixture.merge);
  
  			assert.deepEqual(mQueryOptions.$select, oFixture.after);
  		});
  	});
  
  	//*********************************************************************************************
  	[false, true].forEach(function (bKeys) {
  		QUnit.test("selectKeyProperties: " + (bKeys ? "w/" : "w/o") + " keys", function () {
  			var aKeyProperties = ["foo", "path/to/key"],
  				mQueryOptions = {},
  				oType = bKeys ? {$Key : ["foo", {"alias" : "path/to/key"}]} : {};
  
  			this.mock(_Helper).expects("addToSelect").exactly(bKeys ? 1 : 0)
  				.withExactArgs(sinon.match.same(mQueryOptions), aKeyProperties);
  
  			// code under test
  			_Helper.selectKeyProperties(mQueryOptions, oType);
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("selectKeyProperties: no type metadata available", function () {
  		this.mock(_Helper).expects("addToSelect").never();
  
  		// code under test
  		_Helper.selectKeyProperties({});
  	});
  
  	//*********************************************************************************************
  	[{
  		childPath : "Property",
  		childQueryOptions : {},
  		expected : {$select : ["Property"]}
  	}, {
  		childPath : "NavigationProperty",
  		childQueryOptions : {
  			$select : ["Property"]
  		},
  		expected : {
  			$expand : {
  				"NavigationProperty" : {
  					$select: ["Property", "Property_1", "Property_2"]
  				}
  			}
  		}
  	}, {
  		childPath : "NavigationProperty/Property",
  		childQueryOptions : {},
  		expected : {
  			$expand : {
  				"NavigationProperty" : {
  					$select: ["Property_1", "Property_2", "Property"]
  				}
  			}
  		}
  	}, {
  		childPath : "NavigationProperty/Property_1",
  		childQueryOptions : {},
  		expected : {
  			$expand : {
  				"NavigationProperty" : {
  					$select: ["Property_1", "Property_2"]
  				}
  			}
  		}
  	}, {
  		childPath : "Property/NavigationProperty",
  		childQueryOptions : {},
  		expected : {
  			$expand : {
  				"Property/NavigationProperty" : {
  					$select: ["Property_1", "Property_2"]
  				}
  			}
  		}
  	}, {
  		childPath : "Property_1/Property_2",
  		childQueryOptions : {},
  		expected : {$select : ["Property_1/Property_2"]}
  	}, {
  		childPath : "NavigationProperty_1/NavigationProperty_2",
  		childQueryOptions : {$foo : "bar"}, // will be taken as is
  		expected : {
  			$expand : {
  				"NavigationProperty_1" : {
  					$expand : {
  						"NavigationProperty_2" : {
  							$foo : "bar",
  							$select: ["Property_1", "Property_2"]
  						}
  					},
  					$select: ["Property_1", "Property_2"]
  				}
  			}
  		}
  	}].forEach(function (oFixture) {
  		QUnit.test("wrapChildQueryOptions, " + oFixture.childPath, function (assert) {
  			var oMetaModel = {
  					// Note: "this" not needed, save Function#bind below
  					fetchObject : function () {}
  				},
  				aMetaPathSegments = oFixture.childPath === ""
  					? []
  					: oFixture.childPath.split("/"),
  				mWrappedQueryOptions,
  				oMetaModelMock = this.mock(oMetaModel);
  
  			aMetaPathSegments.forEach(function (sSegment, j, aMetaPathSegments) {
  				var sPropertyMetaPath = "/EMPLOYEES/" + aMetaPathSegments.slice(0, j + 1).join("/"),
  					sKind = sSegment.split("_")[0];
  
  				oMetaModelMock.expects("fetchObject")
  					.withExactArgs(sPropertyMetaPath)
  					.returns(SyncPromise.resolve({$kind : sKind}));
  				if (sKind === "NavigationProperty") {
  					oMetaModelMock.expects("fetchObject")
  						.withExactArgs(sPropertyMetaPath + "/")
  						.returns(SyncPromise.resolve({$Key : ["Property_1", "Property_2"]}));
  				}
  			});
  
  			// code under test
  			mWrappedQueryOptions = _Helper.wrapChildQueryOptions("/EMPLOYEES", oFixture.childPath,
  				oFixture.childQueryOptions, oMetaModel.fetchObject);
  
  			assert.deepEqual(mWrappedQueryOptions, oFixture.expected);
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("wrapChildQueryOptions: empty path", function (assert) {
  		var mChildQueryOptions = {};
  
  		// code under test
  		assert.strictEqual(
  			_Helper.wrapChildQueryOptions("/...", "", mChildQueryOptions),
  			mChildQueryOptions);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("wrapChildQueryOptions, returns undefined if $apply is present", function (assert) {
  		var mChildQueryOptions = {
  				$apply : "filter(Amount gt 3)"
  			},
  			oMetaModel = {
  				// Note: "this" not needed, save Function#bind below
  				fetchObject : function () {}
  			},
  			oMetaModelMock = this.mock(oMetaModel);
  
  		oMetaModelMock.expects("fetchObject")
  			.withExactArgs("/EMPLOYEES/NavigationProperty")
  			.returns(SyncPromise.resolve({$kind : "NavigationProperty"}));
  		oMetaModelMock.expects("fetchObject")
  			.withExactArgs("/EMPLOYEES/NavigationProperty/")
  			.returns(SyncPromise.resolve({}));
  		this.oLogMock.expects("debug").withExactArgs(
  			"Cannot wrap $apply into $expand: NavigationProperty",
  			JSON.stringify(mChildQueryOptions), sClassName
  		);
  
  		// code under test
  		assert.strictEqual(
  			_Helper.wrapChildQueryOptions("/EMPLOYEES", "NavigationProperty", mChildQueryOptions,
  				oMetaModel.fetchObject),
  			undefined);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("wrapChildQueryOptions, child path with bound function", function (assert) {
  		var oMetaModel = {
  				// Note: "this" not needed, save Function#bind below
  				fetchObject : function () {}
  			};
  
  		this.mock(oMetaModel).expects("fetchObject")
  			.withExactArgs("/EMPLOYEES/name.space.boundFunction")
  			.returns(SyncPromise.resolve({$kind : "Function"}));
  
  		// code under test
  		assert.strictEqual(
  			_Helper.wrapChildQueryOptions("/EMPLOYEES", "name.space.boundFunction/Property", {},
  				oMetaModel.fetchObject),
  			undefined);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("wrapChildQueryOptions, structural property w/ query options", function (assert) {
  		var oMetaModel = {
  				// Note: "this" not needed, save Function#bind below
  				fetchObject : function () {}
  			},
  			mChildLocalQueryOptions = {$apply : "filter(AGE gt 42)"};
  
  		this.mock(oMetaModel).expects("fetchObject")
  			.withExactArgs("/EMPLOYEES/Property")
  			.returns(SyncPromise.resolve({$kind : "Property"}));
  		this.oLogMock.expects("error").withExactArgs(
  			"Failed to enhance query options for auto-$expand/$select as the child "
  				+ "binding has query options, but its path 'Property' points to a "
  				+ "structural property",
  			JSON.stringify(mChildLocalQueryOptions), sClassName);
  
  		// code under test
  		assert.strictEqual(
  			_Helper.wrapChildQueryOptions("/EMPLOYEES", "Property", mChildLocalQueryOptions,
  				oMetaModel.fetchObject),
  			undefined);
  	});
  
  	//*********************************************************************************************
  	[{
  		mQueryOptions : undefined,
  		sOrderBy : undefined,
  		aFilters : undefined
  	}, {
  		mQueryOptions : {$orderby : "bar", $select : "Name"},
  		sOrderBy : undefined,
  		aFilters : [undefined]
  	}, {
  		mQueryOptions : undefined,
  		sOrderBy : "foo",
  		aFilters : [""],
  		oResult : {$orderby : "foo"}
  	}, {
  		mQueryOptions : {$orderby : "bar", $select : "Name"},
  		sOrderBy : "foo,bar",
  		aFilters : undefined,
  		oResult : {$orderby : "foo,bar", $select : "Name"}
  	}, {
  		mQueryOptions : {$orderby : "bar", $select : "Name"},
  		sOrderBy : "bar",
  		aFilters : undefined
  	}, {
  		mQueryOptions : undefined,
  		sOrderBy : undefined,
  		aFilters : ["foo"],
  		oResult : {$filter : "foo"}
  	}, {
  		mQueryOptions : {$filter : "bar", $select : "Name"},
  		sOrderBy : undefined,
  		aFilters : ["foo,bar"],
  		oResult : {$filter : "foo,bar", $select : "Name"}
  	}, {
  		mQueryOptions : {$filter: "bar", $select : "Name"},
  		sOrderBy : undefined,
  		aFilters : ["bar"]
  	}, {
  		mQueryOptions : {$filter: "bar", $orderby : "foo", $select : "Name"},
  		sOrderBy : "foo",
  		aFilters : ["bar"]
  	}, {
  		mQueryOptions : {$filter: "foo", $orderby : "bar", $select : "Name"},
  		sOrderBy : "foo,bar",
  		aFilters : ["bar,baz"],
  		oResult : {$filter : "bar,baz", $orderby : "foo,bar", $select : "Name"}
  	}, {
  		mQueryOptions : {$filter : "foo", $orderby : "bar", $select : "Name"},
  		sOrderBy : "foo,bar",
  		aFilters : [undefined, "bar"],
  		oResult : {
  			$$filterBeforeAggregate : "bar",
  			$filter: "foo",
  			$orderby : "foo,bar",
  			$select : "Name"
  		}
  	}, {
  		mQueryOptions : {$filter : "foo", $orderby : "bar", $select : "Name"},
  		sOrderBy : "foo,bar",
  		aFilters : ["", "bar,baz"],
  		oResult : {
  			$$filterBeforeAggregate : "bar,baz",
  			$filter: "foo",
  			$orderby : "foo,bar",
  			$select : "Name"
  		}
  	}, {
  		mQueryOptions : {$filter : "foo", $orderby : "bar", $select : "Name"},
  		sOrderBy : "foo,bar",
  		aFilters : ["bar,baz", "foo,bar"],
  		oResult : {
  			$$filterBeforeAggregate : "foo,bar",
  			$filter : "bar,baz",
  			$orderby : "foo,bar",
  			$select : "Name"
  		}
  	}, {
  		mQueryOptions : {
  			$$filterBeforeAggregate : "baz",
  			$filter : "foo",
  			$orderby : "bar",
  			$select : "Name"},
  		sOrderBy : "foo,bar",
  		aFilters : ["bar,baz"],
  		oResult : {
  			$$filterBeforeAggregate : "baz",
  			$filter : "bar,baz",
  			$orderby : "foo,bar",
  			$select : "Name"
  		}
  	}].forEach(function (oFixture, i) {
  		QUnit.test("mergeQueryOptions, " + i, function (assert) {
  			var oResult,
  				sQueryOptionsJSON = JSON.stringify(oFixture.mQueryOptions);
  
  			// code under test
  			oResult = _Helper.mergeQueryOptions(oFixture.mQueryOptions, oFixture.sOrderBy,
  				oFixture.aFilters);
  
  			assert.strictEqual(JSON.stringify(oFixture.mQueryOptions), sQueryOptionsJSON);
  			if ("oResult" in oFixture) {
  				assert.deepEqual(oResult, oFixture.oResult, i);
  			} else {
  				assert.strictEqual(oResult, oFixture.mQueryOptions, i);
  			}
  			if (oResult) {
  				assert.ok(oResult.$orderby || !("$orderby" in oResult), "$orderby");
  				assert.ok(oResult.$filter || !("$filter" in oResult), "$filter");
  				assert.ok(oResult.$$filterBeforeAggregate
  					|| !("$$filterBeforeAggregate" in oResult), "$$filterBeforeAggregate");
  			}
  		});
  	});
  
  	//*********************************************************************************************
  [{ // $select=Bar
  	mQueryOptions : {
  		$select : "Bar"
  	},
  	sPath : "",
  	mQueryOptionsForPath : {
  		$select : "Bar"
  	}
  }, { // mQueryOptions has to be optional
  	mQueryOptions : undefined,
  	sPath : "",
  	mQueryOptionsForPath : {}
  }, { // mQueryOptions has to be optional
  	mQueryOptions : undefined,
  	sPath : "FooSet",
  	mQueryOptionsForPath : {}
  }, { // $select=Bar
  	mQueryOptions : {
  		$select : "Bar"
  	},
  	sPath : "FooSet/WithoutExpand",
  	mQueryOptionsForPath : {}
  }, { // $expand(FooSet=$expand(BarSet=$select(Baz)))
  	mQueryOptions : {
  		$expand : {
  			FooSet : {
  				$expand : {
  					BarSet : {
  						$select : ["Baz"]
  					}
  				}
  			}
  		}
  	},
  	sPath : "15/FooSet('0815')/BarSet",
  	mQueryOptionsForPath : {
  		$select : ["Baz"]
  	}
  }, { // $expand(ExpandWithoutOptions)
  	mQueryOptions : {
  		$expand : {
  			ExpandWithoutOptions : true
  		}
  	},
  	sPath : "ExpandWithoutOptions",
  	mQueryOptionsForPath : {}
  }, { // $expand(FooSet=$select(Bar,Baz))
  	mQueryOptions : {
  		$expand : {
  			FooSet : {
  				$select : ["Bar", "Baz"]
  			}
  		}
  	},
  	sPath : "FooSet('0815')",
  	mQueryOptionsForPath : {
  		$select : ["Bar", "Baz"]
  	}
  }, {// $expand(FooSet=$expand(BarSet=$select(Baz)))
  	mQueryOptions : {
  		$expand : {
  			FooSet : {
  				$expand : {
  					BarSet : {
  						$select : ["Baz"]
  					}
  				}
  			}
  		}
  	},
  	// combination of key predicate and index is unrealistic ;-)
  	sPath : "FooSet($uid=id-1-23)/12/BarSet",
  	mQueryOptionsForPath : {
  		$select : ["Baz"]
  	}
  }, {
  	mQueryOptions : {
  		$expand : {
  			BarSet : {
  				$select : ["Param1", "Param2"]
  			}
  		},
  		$select : ["Param3", "Param4"]
  	},
  	sPath : "('42')/BarSet",
  	mQueryOptionsForPath : {
  		$select : ["Param1", "Param2"]
  	}
  }, {
  	mQueryOptions : {
  		$select : ["Param3", "Param4"]
  	},
  	sPath : "('42')",
  	mQueryOptionsForPath : {
  		$select : ["Param3", "Param4"]
  	}
  }].forEach(function (oFixture) {
  	QUnit.test("getQueryOptionsForPath " + oFixture.sPath, function (assert) {
  		this.mock(_Helper).expects("getMetaPath").withExactArgs(oFixture.sPath).callThrough();
  
  		// code under test
  		assert.deepEqual(
  			_Helper.getQueryOptionsForPath(oFixture.mQueryOptions, oFixture.sPath),
  			oFixture.mQueryOptionsForPath);
  	});
  });
  
  	//*********************************************************************************************
  [
  	{sPath : "/foo/bar/baz", sResult : "baz"},
  	{sPath : "/foo/bar('baz')", sResult : "('baz')"},
  	{sPath : "/foo/bar", sResult : ""},
  	{sPath : "/foo", sResult : undefined},
  	{sPath : "/foo/barolo", sResult : undefined}
  ].forEach(function (oFixture) {
  	QUnit.test("getRelativePath: " + oFixture.sPath, function (assert) {
  		// code under test
  		assert.strictEqual(_Helper.getRelativePath(oFixture.sPath, "/foo/bar"), oFixture.sResult);
  
  		// code under test
  		assert.strictEqual(_Helper.getRelativePath(oFixture.sPath.slice(1), "foo/bar"),
  			oFixture.sResult);
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("getRelativePath with empty sBasePath", function (assert) {
  		// code under test
  		assert.strictEqual(_Helper.getRelativePath("/Teams('42')", ""), "/Teams('42')");
  		// code under test
  		assert.strictEqual(_Helper.getRelativePath("Teams('42')", ""), "Teams('42')");
  	});
  
  	//*********************************************************************************************
  [{
  	aggregated : {$select : ["Name"]},
  	additional : {$select : ["ID"]},
  	result : {$select : ["Name", "ID"]},
  	title : "merge $select"
  }, {
  	aggregated : {},
  	additional : {$select : ["ID"]},
  	result : {$select : ["ID"]},
  	title : "add $select"
  }, {
  	aggregated : {$select : ["Name"]},
  	additional : {},
  	result : {$select : ["Name"]},
  	title : "no additional $select"
  }, {
  	aggregated : {$select : ["ID", "Name"]},
  	additional : {$select : ["ID"]},
  	result : {$select : ["ID", "Name"]},
  	title : "$select unchanged"
  }, {
  	aggregated : {foo : "bar", $select : ["ID"]},
  	additional : {foo : "bar", $select : ["ID"]},
  	result : {foo : "bar", $select : ["ID"]},
  	title : "custom query option"
  }, {
  	aggregated : {},
  	additional : {$expand : {foo : null}},
  	result : {$expand : {foo : null}},
  	title : "added $expand"
  }, {
  	aggregated : {$expand : {foo : null}},
  	additional : {$expand : {bar : null, baz : null}},
  	result : {$expand : {foo : null, bar : null, baz : null}},
  	title : "added $expand path"
  }].forEach(function (oFixture) {
  	QUnit.test("aggregateQueryOptions: " + oFixture.title, function (assert) {
  		// code under test
  		_Helper.aggregateQueryOptions(oFixture.aggregated, oFixture.additional);
  
  		assert.deepEqual(oFixture.aggregated, oFixture.result);
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("hasPathPrefix", function (assert) {
  		var oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("getRelativePath").withExactArgs("sPath", "sBasePath")
  			.returns(undefined);
  
  		// code under test
  		assert.strictEqual(_Helper.hasPathPrefix("sPath", "sBasePath"), false);
  
  		oHelperMock.expects("getRelativePath").withExactArgs("sPath", "sBasePath").returns("");
  
  		// code under test
  		assert.strictEqual(_Helper.hasPathPrefix("sPath", "sBasePath"), true);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("aggregateQueryOptions: recursion", function () {
  		var mAggregatedQueryOptions = {$expand : {foo : {}}},
  			oHelperMock = this.mock(_Helper),
  			mQueryOptions = {$expand : {foo : {}}};
  
  		oHelperMock.expects("aggregateQueryOptions")
  			.withExactArgs(sinon.match.same(mAggregatedQueryOptions),
  				sinon.match.same(mQueryOptions))
  			.callThrough(); // start the recursion
  		oHelperMock.expects("aggregateQueryOptions")
  			.withExactArgs(sinon.match.same(mAggregatedQueryOptions.$expand.foo),
  				sinon.match.same(mQueryOptions.$expand.foo));
  
  		_Helper.aggregateQueryOptions(mAggregatedQueryOptions, mQueryOptions);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("trampoline properties", function (assert) {
  		assert.strictEqual(_Helper.deepEqual, deepEqual);
  		assert.strictEqual(_Helper.merge, merge);
  		assert.strictEqual(_Helper.uid, uid);
  	});
  
  	//*********************************************************************************************
  [{
  	// no $kind
  },{
  	$kind : "Property"
  }, {
  	$kind : "NavigationProperty"
  }].forEach(function (oFixture, i) {
  	QUnit.test("fetchPropertyAndType: " + i, function (assert) {
  		var oExpectedResult = oFixture.$kind ? { $kind : oFixture.$kind } : undefined,
  			oMetaModel = {
  				fetchObject : function () {}
  			},
  			oMetaModelMock = this.mock(oMetaModel),
  			oPromise,
  			oSyncPromise = SyncPromise.resolve(oExpectedResult);
  
  		oMetaModelMock.expects("fetchObject")
  			.withExactArgs("/resolved/child/metaPath")
  			.returns(oSyncPromise);
  
  		if (oFixture.$kind === "NavigationProperty") {
  			oMetaModelMock.expects("fetchObject")
  				.withExactArgs("/resolved/child/metaPath/")
  				.returns(SyncPromise.resolve());
  		}
  
  		// code under test
  		oPromise = _Helper.fetchPropertyAndType(oMetaModel.fetchObject, "/resolved/child/metaPath");
  
  		assert.strictEqual(oPromise.getResult(), oExpectedResult);
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("extractMergeableQueryOptions", function (assert) {
  		var mQueryOptions;
  
  		// code under test
  		assert.deepEqual(_Helper.extractMergeableQueryOptions({}), {});
  
  		// code under test
  		assert.deepEqual(_Helper.extractMergeableQueryOptions({$count : true}), {});
  
  		mQueryOptions = {$expand : "expand"};
  		// code under test
  		assert.deepEqual(_Helper.extractMergeableQueryOptions(mQueryOptions), {$expand : "expand"});
  		assert.deepEqual(mQueryOptions, {$expand : "~"});
  
  		mQueryOptions = {$select : "select"};
  		// code under test
  		assert.deepEqual(_Helper.extractMergeableQueryOptions(mQueryOptions), {$select : "select"});
  		assert.deepEqual(mQueryOptions, {$select : "~"});
  
  		mQueryOptions = {
  			$count : true,
  			$expand : "expand",
  			$select : "select"
  		};
  		// code under test
  		assert.deepEqual(_Helper.extractMergeableQueryOptions(mQueryOptions), {
  			$expand : "expand",
  			$select : "select"
  		});
  		assert.deepEqual(mQueryOptions, {
  			$count : true,
  			$expand : "~",
  			$select : "~"
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("createMissing: simple properties", function (assert) {
  		var oObject = {bar : undefined};
  
  		// code under test
  		_Helper.createMissing(oObject, ["bar"]);
  
  		assert.deepEqual(oObject, {bar : undefined});
  
  		assert.throws(function () {
  			// code under test
  			_Helper.createMissing(oObject, ["bar", "invalid"]);
  		}, TypeError);
  
  		// code under test
  		_Helper.createMissing(oObject, ["foo"]);
  
  		assert.deepEqual(oObject, {bar : undefined, foo : null});
  
  		// code under test (idempotency)
  		_Helper.createMissing(oObject, ["foo"]);
  
  		assert.deepEqual(oObject, {bar : undefined, foo : null});
  
  		assert.throws(function () {
  			// code under test
  			_Helper.createMissing(oObject, ["foo", "invalid"]);
  		}, TypeError);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("createMissing: paths", function (assert) {
  		var oEmpty = {},
  			oObject = {foo : oEmpty};
  
  		// code under test
  		_Helper.createMissing(oObject, ["foo"]);
  
  		assert.deepEqual(oObject, {foo : {}});
  		assert.strictEqual(oObject.foo, oEmpty);
  
  		// code under test
  		_Helper.createMissing(oObject, ["foo", "bar"]);
  
  		assert.deepEqual(oObject, {foo : {bar : null}});
  		assert.strictEqual(oObject.foo, oEmpty, "originally empty object has been modified");
  
  		assert.throws(function () {
  			// code under test
  			_Helper.createMissing(oObject, ["foo", "bar", "invalid"]);
  		}, TypeError);
  
  		// code under test
  		_Helper.createMissing(oObject, ["a", "b", "c"]);
  
  		assert.deepEqual(oObject, {
  			a : {
  				b : {
  					c : null
  				}
  			},
  			foo : {
  				bar : null
  			}
  		});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("inheritPathValue: simple properties", function (assert) {
  		var oSource = {bar : "<bar>", foo : "<foo>"},
  			sSource = JSON.stringify(oSource),
  			oTarget = {bar : undefined};
  
  		// code under test
  		_Helper.inheritPathValue(["foo"], oSource, oTarget);
  
  		assert.deepEqual(oTarget, {bar : undefined, foo : "<foo>"});
  
  		// code under test
  		_Helper.inheritPathValue(["bar"], oSource, oTarget);
  
  		assert.deepEqual(oTarget, {bar : undefined, foo : "<foo>"});
  		assert.strictEqual(JSON.stringify(oSource), sSource, "unchanged");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("inheritPathValue: paths", function (assert) {
  		var oEmpty = {},
  			oSource = {
  				foo : {
  					bar : "<bar>"
  				}
  			},
  			sSource = JSON.stringify(oSource),
  			oTarget0 = {},
  			oTarget1 = {
  				foo : oEmpty
  			},
  			oTarget2 = {
  				foo : {
  					bar : undefined
  				}
  			};
  
  		// code under test
  		_Helper.inheritPathValue(["foo", "bar"], oSource, oTarget0);
  
  		assert.deepEqual(oTarget0, {
  			foo : {
  				bar : "<bar>"
  			}
  		});
  
  		// code under test
  		_Helper.inheritPathValue(["foo", "baz"], oSource, oTarget0);
  
  		assert.deepEqual(oTarget0, {
  			foo : {
  				bar : "<bar>",
  				baz : undefined // we don't care that "baz" was actually missing in source
  			}
  		});
  
  		// code under test
  		_Helper.inheritPathValue(["foo", "bar"], oSource, oTarget1);
  
  		assert.deepEqual(oTarget1, {
  			foo : {
  				bar : "<bar>"
  			}
  		});
  		assert.strictEqual(oTarget1.foo, oEmpty, "originally empty object has been modified");
  
  		// code under test
  		_Helper.inheritPathValue(["foo", "bar"], oSource, oTarget2);
  
  		assert.deepEqual(oTarget2, {
  			foo : {
  				bar : undefined
  			}
  		});
  
  		assert.strictEqual(JSON.stringify(oSource), sSource, "unchanged");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("inheritPathValue: errors", function (assert) {
  		var oSource = {
  				foo : {
  					bar : "<bar>"
  				}
  			};
  
  		assert.throws(function () {
  			// code under test
  			_Helper.inheritPathValue(["foo", "bar"], oSource, {foo : null});
  		}, TypeError);
  
  		assert.throws(function () {
  			// code under test
  			_Helper.inheritPathValue(["bar", "foo"], oSource, {});
  		}, TypeError);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("decomposeError: w/o ContentID, w/o details, w/o target", function (assert) {
  		var oError = new Error("Message"),
  			aErrors;
  
  		oError.error = {message : "Top level message"};
  		oError.status = 500;
  		oError.statusText = "Internal Server Error";
  
  		this.mock(_Helper).expects("getContentID").withExactArgs(sinon.match.same(oError.error))
  			.returns(undefined);
  
  		// code under test
  		aErrors = _Helper.decomposeError(oError, [{
  				$ContentID : "0.0",
  				$resourcePath : "~path0~",
  				url: "~url0~"
  			}, {
  				$ContentID : "1.0",
  				$resourcePath : "~path1~",
  				url: "~url1~"
  			}]);
  
  		assert.strictEqual(aErrors.length, 2);
  		checkClonedError(assert, oError, aErrors[0], {
  				message : "Top level message"
  			}, "~url0~", "~path0~");
  		checkClonedError(assert, oError, aErrors[1], {
  				message : "Top level message",
  				$ignoreTopLevel : true
  			}, "~url1~", "~path1~");
  	});
  
  	//*********************************************************************************************
  ["n/a", ""].forEach(function (sTarget) {
  	var sTitle = "decomposeError: w/o ContentID, w/ details, w/ + w/o target, top level target: "
  			+ sTarget;
  
  	QUnit.test(sTitle, function (assert) {
  		var oError = new Error("Message"),
  			aErrors,
  			oHelperMock = this.mock(_Helper);
  
  		oError.error = {
  			message : "Top level message",
  			target : sTarget,
  			details : [{
  				foo : "bar",
  				message : "A Message",
  				target : "n/a"
  			}, {
  				foo : "baz",
  				message : "Another Message"
  			}, {
  				foo : "barbaz",
  				message : "Yet another Message",
  				target : ""
  			}]
  		};
  		oError.status = 500;
  		oError.statusText = "Internal Server Error";
  		oHelperMock.expects("getContentID").withExactArgs(sinon.match.same(oError.error))
  			.returns(undefined);
  		oHelperMock.expects("getContentID").withExactArgs(oError.error.details[0])
  			.returns(undefined);
  		oHelperMock.expects("getContentID").withExactArgs(oError.error.details[1])
  			.returns(undefined);
  		oHelperMock.expects("getContentID").withExactArgs(oError.error.details[2])
  			.returns(undefined);
  
  		// code under test
  		aErrors = _Helper.decomposeError(oError, [{
  				$ContentID : "0.0",
  				$resourcePath : "~path0~",
  				url: "~url0~"
  			}, {
  				$ContentID : "1.0",
  				$resourcePath : "~path1~",
  				url: "~url1~"
  			}, {
  				$ContentID : "2.0",
  				$resourcePath : "~path2~",
  				url: "~url2~"
  			}]);
  
  		assert.strictEqual(aErrors.length, 3);
  		checkClonedError(assert, oError, aErrors[0], {
  				message : sTarget ? "n/a: Top level message" : "Top level message",
  				details : [{
  					foo : "bar",
  					message : "n/a: A Message"
  				}, {
  					foo : "baz",
  					message : "Another Message"
  				}, {
  					foo : "barbaz",
  					message : "Yet another Message"
  				}]
  			}, "~url0~", "~path0~");
  		checkClonedError(assert, oError, aErrors[1], {
  				message : "Top level message",
  				target : sTarget,
  				details : [],
  				$ignoreTopLevel : true
  			}, "~url1~", "~path1~");
  		checkClonedError(assert, oError, aErrors[2], {
  				message : "Top level message",
  				target : sTarget,
  				details : [],
  				$ignoreTopLevel : true
  			}, "~url2~", "~path2~");
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("decomposeError: w/ ContentID", function (assert) {
  		var oError = new Error("Message"),
  			aErrors,
  			oHelperMock = this.mock(_Helper);
  
  		oError.error = {
  			message : "Top level message",
  			"@SAP__core.ContentID" : "1.0",
  			details : [{
  				message : "A message",
  				"@SAP__core.ContentID" : "1.0"
  			}]
  		};
  		oError.status = 500;
  		oError.statusText = "Internal Server Error";
  		oHelperMock.expects("getContentID").withExactArgs(sinon.match.same(oError.error))
  			.callThrough();
  		oHelperMock.expects("getContentID").withExactArgs(oError.error.details[0])
  			.callThrough();
  
  		// code under test
  		aErrors = _Helper.decomposeError(oError, [{
  				$ContentID : "0.0",
  				$resourcePath : "~path0~",
  				url: "~url0~"
  			}, {
  				$ContentID : "1.0",
  				$resourcePath : "~path1~",
  				url: "~url1~"
  			}]);
  
  		assert.strictEqual(aErrors.length, 2);
  		checkClonedError(assert, oError, aErrors[0], {
  				message : "Top level message",
  				"@SAP__core.ContentID" : "1.0",
  				$ignoreTopLevel : true,
  				details : []
  			}, "~url0~", "~path0~");
  		checkClonedError(assert, oError, aErrors[1], {
  				message : "Top level message",
  				"@SAP__core.ContentID" : "1.0",
  				details : [{
  					message : "A message",
  					"@SAP__core.ContentID" : "1.0"
  				}]
  			}, "~url1~", "~path1~");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getAnnotation: success", function (assert) {
  		var oMessage = {"~key~" : "~value~"};
  
  		this.mock(_Helper).expects("getAnnotationKey")
  			.withExactArgs(sinon.match.same(oMessage), "~sName~").returns("~key~");
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotation(oMessage, "~sName~"), "~value~");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getAnnotation: failure", function (assert) {
  		var oMessage = {"undefined" : "~value~"};
  
  		this.mock(_Helper).expects("getAnnotationKey")
  			.withExactArgs(sinon.match.same(oMessage), "~sName~").returns(undefined);
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotation(oMessage, "~sName~"), undefined);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getAnnotationKey", function (assert) {
  		// code under test
  		assert.strictEqual(_Helper.getAnnotationKey({}, ".AnyAnnotation"), undefined);
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotationKey({"@Foo.NoAnyAnnotation" : "n/a"},
  			".AnyAnnotation"), undefined);
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotationKey({"@Foo.Bar" : "n/a"}, "bar"), undefined);
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotationKey({"message@Core.AnyAnnotation" : "n/a"},
  			".AnyAnnotation"), undefined);
  
  		this.oLogMock.expects("warning")
  			.withExactArgs("Cannot distinguish @Core.AnyAnnotation from @SAP__core.AnyAnnotation",
  				undefined, sClassName);
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotationKey({
  				"@Core.AnyAnnotation" : "1.0",
  				"@SAP__core.AnyAnnotation" : "1.0"
  			}, ".AnyAnnotation"), undefined, "duplicate alias");
  
  		this.oLogMock.expects("warning")
  			.withExactArgs("Cannot distinguish @Core.AnyAnnotation from @Foo.AnyAnnotation",
  				undefined, sClassName);
  		this.oLogMock.expects("warning")
  			.withExactArgs("Cannot distinguish @Foo.AnyAnnotation from @SAP__core.AnyAnnotation",
  				undefined, sClassName);
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotationKey({
  				"@Core.AnyAnnotation" : "1.0",
  				"@Foo.AnyAnnotation" : "1.0",
  				"@SAP__core.AnyAnnotation" : "1.0"
  			}, ".AnyAnnotation"), undefined, "toggling sAnyAnnotation is not enough");
  
  		// code under test
  		assert.strictEqual(_Helper.getAnnotationKey({"@SAP__core.AnyAnnotation" : "1.0"},
  			".AnyAnnotation"), "@SAP__core.AnyAnnotation");
  
  		// code under test
  		assert.strictEqual(
  			_Helper.getAnnotationKey({"@Core.AnyAnnotation" : "2.1"},".AnyAnnotation"),
  			"@Core.AnyAnnotation");
  
  		// code under test
  		assert.strictEqual(
  			_Helper.getAnnotationKey({"@Org.OData.Core.V1.AnyAnnotation" : "3.2"},
  				".AnyAnnotation"),
  			"@Org.OData.Core.V1.AnyAnnotation");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getContentID", function (assert) {
  		var oMessage = {};
  
  		this.mock(_Helper).expects("getAnnotation")
  			.withExactArgs(sinon.match.same(oMessage), ".ContentID")
  			.returns("~ContentID~");
  
  		assert.strictEqual(_Helper.getContentID(oMessage), "~ContentID~");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getAdditionalTargets", function (assert) {
  		var oMessage = {};
  
  		this.mock(_Helper).expects("getAnnotation")
  			.withExactArgs(sinon.match.same(oMessage), ".additionalTargets")
  			.returns("~additionalTargets~");
  
  		assert.strictEqual(_Helper.getAdditionalTargets(oMessage), "~additionalTargets~");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("filterPaths", function (assert) {
  		var aPaths = ["/foo", "/bar/baz"];
  
  		// code under test
  		assert.deepEqual(_Helper.filterPaths(aPaths, ["/baz/qux"]), ["/baz/qux"]);
  
  		// code under test
  		assert.deepEqual(_Helper.filterPaths(aPaths, ["/foo"]), []);
  
  		// code under test
  		assert.deepEqual(_Helper.filterPaths(aPaths, ["/foobar", "/bar/baz/qux"]),
  			["/foobar"]);
  
  		// code under test
  		assert.deepEqual(_Helper.filterPaths(aPaths, ["/bar(42)/baz", "/qux"]), ["/qux"]);
  	});
  
  	//*********************************************************************************************
  	QUnit.test("adjustTargetsInError: technical error", function () {
  		var oError = {};
  
  		this.mock(_Helper).expects("adjustTargets").never();
  
  		// code under test
  		_Helper.adjustTargetsInError(oError, "oOperationMetadata", "sParameterContextPath",
  			"sContextPath");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("adjustTargetsInError: no details", function () {
  		var oError = {error : {}};
  
  		this.mock(_Helper).expects("adjustTargets")
  			.withExactArgs(sinon.match.same(oError.error), "oOperationMetadata",
  				"sParameterContextPath", "sContextPath");
  
  		// code under test
  		_Helper.adjustTargetsInError(oError, "oOperationMetadata", "sParameterContextPath",
  			"sContextPath");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("adjustTargetsInError: with details", function () {
  		var oDetail0 = {},
  			oDetail1 = {},
  			oError = {
  				error : {
  					details : [oDetail0, oDetail1]
  				}
  			},
  			oHelperMock = this.mock(_Helper);
  
  		oHelperMock.expects("adjustTargets")
  			.withExactArgs(sinon.match.same(oError.error), "oOperationMetadata",
  				"sParameterContextPath", "sContextPath");
  		oHelperMock.expects("adjustTargets")
  			.withExactArgs(sinon.match.same(oDetail0), "oOperationMetadata",
  				"sParameterContextPath", "sContextPath");
  		oHelperMock.expects("adjustTargets")
  			.withExactArgs(sinon.match.same(oDetail1), "oOperationMetadata",
  					"sParameterContextPath", "sContextPath");
  
  		// code under test
  		_Helper.adjustTargetsInError(oError, "oOperationMetadata", "sParameterContextPath",
  			"sContextPath");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("adjustTargets: no target", function (assert) {
  		var oMessage = {};
  
  		this.mock(_Helper).expects("getAnnotationKey")
  			.withExactArgs(sinon.match.same(oMessage), ".additionalTargets")
  			.returns(undefined);
  		this.mock(_Helper).expects("getAdjustedTarget").never();
  
  		// code under test
  		_Helper.adjustTargets(oMessage, "oOperationMetadata", "sParameterContextPath",
  			"sContextPath");
  
  		assert.deepEqual(oMessage, {target : undefined});
  	});
  
  	//*********************************************************************************************
  	QUnit.test("adjustTargets: without additional targets", function (assert) {
  		var oMessage = {target : "target"};
  
  		this.mock(_Helper).expects("getAnnotationKey")
  			.withExactArgs(sinon.match.same(oMessage), ".additionalTargets")
  			.returns(undefined);
  		this.mock(_Helper).expects("getAdjustedTarget")
  			.withExactArgs("target", "oOperationMetadata", "sParameterContextPath", "sContextPath")
  			.returns("~adjusted~");
  
  		// code under test
  		_Helper.adjustTargets(oMessage, "oOperationMetadata", "sParameterContextPath",
  			"sContextPath");
  
  		assert.deepEqual(oMessage, {target : "~adjusted~"});
  	});
  
  	//*********************************************************************************************
  [true, false].forEach(function (bTargetIsValid) {
  	var sTitle = "adjustTargets: with additional targets, target is valid: " + bTargetIsValid;
  
  	QUnit.test(sTitle, function (assert) {
  		var oHelperMock = this.mock(_Helper),
  			oMessage = {
  				target : "target",
  				"@foo.additionalTargets" : ["additional1", "foo", "additional2"]
  			};
  
  		oHelperMock.expects("getAnnotationKey")
  			.withExactArgs(sinon.match.same(oMessage), ".additionalTargets")
  			.returns("@foo.additionalTargets");
  		oHelperMock.expects("getAdjustedTarget")
  			.withExactArgs("target", "oOperationMetadata", "sParameterContextPath", "sContextPath")
  			.returns(bTargetIsValid ? "~adjusted~" : undefined);
  		oHelperMock.expects("getAdjustedTarget")
  			.withExactArgs("additional1", "oOperationMetadata", "sParameterContextPath",
  				"sContextPath")
  			.returns("~adjusted1~");
  		oHelperMock.expects("getAdjustedTarget")
  			.withExactArgs("foo", "oOperationMetadata", "sParameterContextPath",
  				"sContextPath")
  			.returns(undefined);
  		oHelperMock.expects("getAdjustedTarget")
  			.withExactArgs("additional2", "oOperationMetadata", "sParameterContextPath",
  				"sContextPath")
  			.returns("~adjusted2~");
  
  		// code under test
  		_Helper.adjustTargets(oMessage, "oOperationMetadata", "sParameterContextPath",
  			"sContextPath");
  
  		if (bTargetIsValid) {
  			assert.deepEqual(oMessage, {
  				target : "~adjusted~",
  				"@foo.additionalTargets" : ["~adjusted1~", "~adjusted2~"]
  			});
  		} else {
  			assert.deepEqual(oMessage, {
  				target : "~adjusted1~",
  				"@foo.additionalTargets" : ["~adjusted2~"]
  			});
  		}
  	});
  });
  
  	//*********************************************************************************************
  	QUnit.test("getAdjustedTarget: unbound operation", function (assert) {
  		var oOperationMetadata = {
  				$Parameter : [{$Name : "baz"}, {$Name : "foo"}]
  			};
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getAdjustedTarget("bar", oOperationMetadata, "~parameterContextPath~"),
  			undefined);
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getAdjustedTarget("foo", oOperationMetadata, "~parameterContextPath~"),
  			"~parameterContextPath~/foo");
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getAdjustedTarget("foo/bar", oOperationMetadata, "~parameterContextPath~"),
  			"~parameterContextPath~/foo/bar");
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getAdjustedTarget("$Parameter/foo/bar", oOperationMetadata,
  				"~parameterContextPath~"),
  			"~parameterContextPath~/foo/bar");
  	});
  
  	//*********************************************************************************************
  	QUnit.test("getAdjustedTarget: bound operation", function (assert) {
  		var oOperationMetadata = {
  				$IsBound : true,
  				$Parameter : [{$Name : "foo"}]
  			};
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getAdjustedTarget("$Parameter/foo/bar", oOperationMetadata,
  				"~parameterContextPath~", "~contextPath~"),
  			"~contextPath~/bar");
  
  		assert.strictEqual(
  			// code under test
  			_Helper.getAdjustedTarget("$Parameter/bar", oOperationMetadata,
  				"~parameterContextPath~", "~contextPath~"),
  			undefined);
  	});
  });