diff --git a/src/sap.ui.core/src/sap/ui/model/odata/v4/ODataModel.js b/src/sap.ui.core/src/sap/ui/model/odata/v4/ODataModel.js
index 14979e3c88ad..cf6f5ead9027 100644
--- a/src/sap.ui.core/src/sap/ui/model/odata/v4/ODataModel.js
+++ b/src/sap.ui.core/src/sap/ui/model/odata/v4/ODataModel.js
@@ -355,10 +355,11 @@ sap.ui.define([
this.mMetadataHeaders = {"Accept-Language" : sLanguageTag};
mQueryParams = Object.assign({}, mUriParameters, mParameters.metadataUrlParams);
+ const fnGetOrCreateRetryAfterPromise = this.getOrCreateRetryAfterPromise.bind(this);
this.oMetaModel = new ODataMetaModel(
_MetadataRequestor.create(this.mMetadataHeaders, sODataVersion,
mParameters.ignoreAnnotationsFromMetadata, mQueryParams,
- mParameters.withCredentials),
+ mParameters.withCredentials, fnGetOrCreateRetryAfterPromise),
this.sServiceUrl + "$metadata", mParameters.annotationURI, this,
mParameters.supportReferences, mQueryParams["sap-language"]);
this.oInterface = {
@@ -372,10 +373,8 @@ sap.ui.define([
getGroupProperty : this.getGroupProperty.bind(this),
getMessagesByPath : this.getMessagesByPath.bind(this),
getOptimisticBatchEnabler : this.getOptimisticBatchEnabler.bind(this),
+ getOrCreateRetryAfterPromise : fnGetOrCreateRetryAfterPromise,
getReporter : this.getReporter.bind(this),
- getRetryAfterHandler : function () {
- return that.fnRetryAfter;
- },
isIgnoreETag : function () {
return that.bIgnoreETag;
},
@@ -428,6 +427,7 @@ sap.ui.define([
// ensure the events are respectively fired once for a GET request
this.mPath2DataRequestedCount = {};
this.fnRetryAfter = null;
+ this.oRetryAfterPromise = null;
}
/**
@@ -1642,6 +1642,8 @@ sap.ui.define([
this.oRequestor.destroy();
this.mHeaders = undefined;
this.mMetadataHeaders = undefined;
+ this.oRetryAfterPromise = undefined;
+
return Model.prototype.destroy.apply(this, arguments);
};
@@ -2092,6 +2094,35 @@ sap.ui.define([
return this.fnOptimisticBatchEnabler;
};
+ /**
+ * Returns the promise that is currently being used for "Retry-After" handling. Returns
+ * null
if no "Retry-After" is currently known. Creates a new promise if there is
+ * none, an error is given, and a {@link #setRetryAfterHandler handler} is known.
+ *
+ * @param {Error} [oRetryAfterError] - A "Retry-After" error from a backend call
+ * @returns {Promise|null} The current "Retry-After" promise
+ *
+ * @private
+ */
+ ODataModel.prototype.getOrCreateRetryAfterPromise = function (oRetryAfterError) {
+ if (!this.oRetryAfterPromise && this.fnRetryAfter && oRetryAfterError) {
+ this.oRetryAfterPromise = this.fnRetryAfter(oRetryAfterError);
+ this.oRetryAfterPromise.finally(() => {
+ this.oRetryAfterPromise = null;
+ }).catch(() => { /* catch is only needed due to finally */ });
+ this.oRetryAfterPromise.catch((oError) => {
+ // own error reason is not reported to the message model
+ if (oError === oRetryAfterError) {
+ this.reportError(oError.message, sClassName, oError);
+ } else {
+ oError.$reported = true;
+ }
+ });
+ }
+
+ return this.oRetryAfterPromise;
+ };
+
/**
* Method not supported
*
diff --git a/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_MetadataRequestor.js b/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_MetadataRequestor.js
index 9053c07b004c..8237bc41008b 100644
--- a/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_MetadataRequestor.js
+++ b/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_MetadataRequestor.js
@@ -28,17 +28,20 @@ sap.ui.define([
* is deleted(!) after the first read
for a metadata document.
* @param {boolean} [bWithCredentials]
* Whether the XHR should be called with withCredentials
+ * @param {function} fnGetOrCreateRetryAfterPromise
+ * A function that returns or creates the "Retry-After" promise
* @returns {object}
* A new MetadataRequestor object
*/
create : function (mHeaders, sODataVersion, bIgnoreAnnotationsFromMetadata, mQueryParams,
- bWithCredentials) {
+ bWithCredentials, fnGetOrCreateRetryAfterPromise) {
var mUrl2Promise = {},
sQuery = _Helper.buildQuery(mQueryParams);
return {
/**
- * Reads a metadata document from the given URL.
+ * Reads a metadata document from the given URL, taking care of "Retry-After".
+ *
* @param {string} sUrl
* The URL of a metadata document, it must not contain a query string or a
* fragment part
@@ -84,38 +87,53 @@ sap.ui.define([
delete mUrl2Promise[sUrl];
} else {
oPromise = new Promise(function (fnResolve, fnReject) {
- const oAjaxSettings = {
- method : "GET",
- headers : mHeaders
- };
- if (bWithCredentials) {
- oAjaxSettings.xhrFields = {withCredentials : true};
- }
+ function send() {
+ const oAjaxSettings = {
+ method : "GET",
+ headers : mHeaders
+ };
+ if (bWithCredentials) {
+ oAjaxSettings.xhrFields = {withCredentials : true};
+ }
+ jQuery.ajax(bAnnotations ? sUrl : sUrl + sQuery, oAjaxSettings)
+ .then(function (oData, _sTextStatus, jqXHR) {
+ var sDate = jqXHR.getResponseHeader("Date"),
+ sETag = jqXHR.getResponseHeader("ETag"),
+ oJSON = {$XML : oData},
+ sLastModified = jqXHR.getResponseHeader("Last-Modified");
- jQuery.ajax(bAnnotations ? sUrl : sUrl + sQuery, oAjaxSettings)
- .then(function (oData, _sTextStatus, jqXHR) {
- var sDate = jqXHR.getResponseHeader("Date"),
- sETag = jqXHR.getResponseHeader("ETag"),
- oJSON = {$XML : oData},
- sLastModified = jqXHR.getResponseHeader("Last-Modified");
+ if (sDate) {
+ oJSON.$Date = sDate;
+ }
+ if (sETag) {
+ oJSON.$ETag = sETag;
+ }
+ if (sLastModified) {
+ oJSON.$LastModified = sLastModified;
+ }
+ fnResolve(oJSON);
+ }, function (jqXHR) {
+ var oError
+ = _Helper.createError(jqXHR, "Could not load metadata");
- if (sDate) {
- oJSON.$Date = sDate;
- }
- if (sETag) {
- oJSON.$ETag = sETag;
- }
- if (sLastModified) {
- oJSON.$LastModified = sLastModified;
- }
- fnResolve(oJSON);
- }, function (jqXHR, _sTextStatus, _sErrorMessage) {
- var oError = _Helper.createError(jqXHR, "Could not load metadata");
+ if (jqXHR.status === 503
+ && jqXHR.getResponseHeader("Retry-After")
+ && fnGetOrCreateRetryAfterPromise(oError)) {
+ fnGetOrCreateRetryAfterPromise().then(send, fnReject);
+ } else {
+ Log.error("GET " + sUrl, oError.message,
+ "sap.ui.model.odata.v4.lib._MetadataRequestor");
+ fnReject(oError);
+ }
+ });
+ }
- Log.error("GET " + sUrl, oError.message,
- "sap.ui.model.odata.v4.lib._MetadataRequestor");
- fnReject(oError);
- });
+ const oRetryAfterPromise = fnGetOrCreateRetryAfterPromise();
+ if (oRetryAfterPromise) {
+ oRetryAfterPromise.then(send, fnReject);
+ } else {
+ send();
+ }
if (!bAnnotations
&& mQueryParams && "sap-context-token" in mQueryParams) {
delete mQueryParams["sap-context-token"];
diff --git a/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_Requestor.js b/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_Requestor.js
index 5e61280dc795..9dff2a7e83f6 100644
--- a/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_Requestor.js
+++ b/src/sap.ui.core/src/sap/ui/model/odata/v4/lib/_Requestor.js
@@ -89,7 +89,6 @@ sap.ui.define([
this.oModelInterface = oModelInterface;
this.oOptimisticBatch = null; // optimistic batch processing off
this.sQueryParams = _Helper.buildQuery(mQueryParams); // Used for $batch and CSRF token only
- this.oRetryAfterPromise = null;
this.mRunningChangeRequests = {}; // map from group ID to a SyncPromise[]
this.iSessionTimer = 0;
this.iSerialNumber = 0;
@@ -2080,26 +2079,9 @@ sap.ui.define([
send(true);
}, fnReject);
} else if (jqXHR.status === 503 && jqXHR.getResponseHeader("Retry-After")
- && (that.oRetryAfterPromise
- || that.oModelInterface.getRetryAfterHandler())) {
- if (!that.oRetryAfterPromise) {
- const oRetryAfterError = _Helper.createError(jqXHR, "");
- that.oRetryAfterPromise = that.oModelInterface.getRetryAfterHandler()(
- oRetryAfterError);
- that.oRetryAfterPromise.finally(() => {
- that.oRetryAfterPromise = null;
- }).catch(() => { /* catch is only needed due to finally */ });
- that.oRetryAfterPromise.catch((oError) => {
- // own error reason is not reported to the message model
- if (oError === oRetryAfterError) {
- that.oModelInterface
- .reportError(oError.message, sClassName, oError);
- } else {
- oError.$reported = true;
- }
- });
- }
- that.oRetryAfterPromise.then(send, fnReject);
+ && that.oModelInterface.getOrCreateRetryAfterPromise(
+ _Helper.createError(jqXHR, ""))) {
+ that.oModelInterface.getOrCreateRetryAfterPromise().then(send, fnReject);
} else {
sMessage = "Communication error";
if (sContextId) {
@@ -2119,8 +2101,9 @@ sap.ui.define([
});
}
- if (that.oRetryAfterPromise) {
- that.oRetryAfterPromise.then(send, fnReject);
+ const oRetryAfterPromise = that.oModelInterface.getOrCreateRetryAfterPromise();
+ if (oRetryAfterPromise) {
+ oRetryAfterPromise.then(send, fnReject);
} else if (that.oSecurityTokenPromise && sMethod !== "GET") {
that.oSecurityTokenPromise.then(send);
} else {
@@ -2335,13 +2318,12 @@ sap.ui.define([
* @param {function} oModelInterface.getOptimisticBatchEnabler
* A function that returns a callback function which controls the optimistic batch handling,
* see also {@link sap.ui.model.odata.v4.ODataModel#setOptimisticBatchEnabler}
+ * @param {function} oModelInterface.getOrCreateRetryAfterPromise
+ * A function that returns or creates the "Retry-After" promise
* @param {function} oModelInterface.getReporter
* A catch handler function expecting an Error
instance. This function will call
* {@link sap.ui.model.odata.v4.ODataModel#reportError} if the error has not been reported
* yet
- * @param {function} oModelInterface.getRetryAfterHandler
- * A function that returns the "Retry-After" handler,
- * see also {@link sap.ui.model.odata.v4.ODataModel#setRetryAfterHandler}
* @param {function():boolean} oModelInterface.isIgnoreETag
* Tells whether an entity's ETag should be actively ignored (If-Match:*) for PATCH requests.
* @param {function} oModelInterface.onCreateGroup
diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataMetaModel.qunit.js b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataMetaModel.qunit.js
index 464c97453910..121d44eea10c 100644
--- a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataMetaModel.qunit.js
+++ b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataMetaModel.qunit.js
@@ -5500,7 +5500,7 @@ sap.ui.define([
// Note: "ab-CD" is derived from Localization.getLanguageTag here, not from mHeaders!
this.mock(_MetadataRequestor).expects("create")
.withExactArgs({"Accept-Language" : "ab-CD"}, "4.0", undefined,
- {"sap-language" : "~sLanguage~"}, undefined);
+ {"sap-language" : "~sLanguage~"}, undefined, sinon.match.func);
const oCopyAnnotationsExpectation
= this.mock(ODataMetaModel.prototype).expects("_copyAnnotations")
.withExactArgs(sinon.match.same(oMetaModel));
@@ -5545,7 +5545,8 @@ sap.ui.define([
.withArgs(false + "/Foo1/ValueListService/").callThrough();
// observe metadataUrlParams NOT being passed along
this.mock(_MetadataRequestor).expects("create")
- .withExactArgs({"Accept-Language" : "ab-CD"}, "4.0", undefined, {}, undefined);
+ .withExactArgs({"Accept-Language" : "ab-CD"}, "4.0", undefined, {}, undefined,
+ sinon.match.func);
// code under test
oSharedModel = oMetaModel.getOrCreateSharedModel("../ValueListService/$metadata",
diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.integration.qunit.js b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.integration.qunit.js
index 9b2a112cc275..7c86b5a7d0d4 100644
--- a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.integration.qunit.js
+++ b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.integration.qunit.js
@@ -9476,6 +9476,86 @@ sap.ui.define([
TestUtils.onRequest(null);
});
+ //*********************************************************************************************
+ // Scenario: "Retry-After" handling: A request for $metadata is the 1st to encounter 503.
+ // JIRA: CPOUI5ODATAV4-2770
+ QUnit.test('503, "Retry-After" handling: $metadata', async function (assert) {
+ const aBatchPayloads = [];
+ TestUtils.onRequest((sPayload, sRequestLine) => {
+ aBatchPayloads.push(sPayload || sRequestLine);
+ });
+
+ let bAnswerWith503 = false;
+ const oModel = this.createModel(sTeaBusi, {}, {
+ // initial GET works just fine
+ [sTeaBusi + "$metadata"] : {source : "odata/v4/data/metadata.xml"},
+ "/sap/opu/odata4/IWBEP/TEA/default/iwbep/tea_busi_product/0001/$metadata"
+ : [{ // cross-service reference may fail at first try
+ code : 503,
+ headers : {"Retry-After" : "42"},
+ ifMatch : () => bAnswerWith503,
+ message : {
+ error : {code : "DB_MIGRATION", message : "DB migration in progress"}
+ }
+ }, {
+ source : "odata/v4/data/metadata_tea_busi_product.xml"
+ }]
+ });
+ oModel.$keepSend = true; // do not stub sendBatch/-Request
+
+ let iCallbackCount = 0;
+ let fnResolveRetryAfter;
+ oModel.setRetryAfterHandler((oError) => {
+ iCallbackCount += 1;
+ assert.ok(oError instanceof Error);
+ assert.strictEqual(oError.message, "DB migration in progress");
+ const iSeconds = (oError.retryAfter.getTime() - Date.now()) / 1000;
+ assert.ok(iSeconds > 41 && iSeconds < 43, `${iSeconds} roughly 42 seconds`);
+ return new Promise((resolve) => {
+ fnResolveRetryAfter = resolve;
+ });
+ });
+
+ await this.createView(assert, "", oModel);
+
+ assert.deepEqual(aBatchPayloads, [], "no requests yet");
+
+ assert.strictEqual(
+ await oModel.getMetaModel().requestObject("/TEAMS/MEMBER_COUNT/$Type"),
+ "Edm.Int32");
+
+ assert.strictEqual(aBatchPayloads.shift(), "GET " + sTeaBusi + "$metadata",
+ "main $metadata");
+ assert.deepEqual(aBatchPayloads, []);
+
+ bAnswerWith503 = true;
+
+ // code under test
+ const oPromise = oModel.getMetaModel().requestObject(
+ "/TEAMS/TEAM_2_EMPLOYEES/EMPLOYEE_2_EQUIPMENTS/EQUIPMENT_2_PRODUCT/ID/$Type");
+
+ await resolveLater(undefined, /*iDelay*/10); // autoRespondAfter defaults to 10ms
+
+ const sExpectedRequestLine
+ = "GET /sap/opu/odata4/IWBEP/TEA/default/iwbep/tea_busi_product/0001/$metadata";
+ assert.strictEqual(aBatchPayloads.shift(), sExpectedRequestLine,
+ "cross-service reference $metadata failed at first try");
+ assert.deepEqual(aBatchPayloads, []);
+ assert.strictEqual(iCallbackCount, 1);
+
+ bAnswerWith503 = false;
+ fnResolveRetryAfter();
+
+ assert.strictEqual(await oPromise, "Edm.Int32");
+
+ assert.strictEqual(aBatchPayloads.shift(), sExpectedRequestLine,
+ "cross-service reference $metadata succeeded at second try");
+ assert.deepEqual(aBatchPayloads, []);
+ assert.strictEqual(iCallbackCount, 1, "not called again");
+
+ TestUtils.onRequest(null);
+ });
+
//*********************************************************************************************
// Scenario: Table gets a binding context for which data was already loaded and then a refresh
// is performed synchronously.
diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.qunit.js b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.qunit.js
index 2a2a71c603e3..ff91e709e12d 100644
--- a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.qunit.js
+++ b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.qunit.js
@@ -119,10 +119,10 @@ sap.ui.define([
.withExactArgs({}, false, true).returns({"sap-client" : "279"});
this.mock(Supportability).expects("isStatisticsEnabled")
.withExactArgs().returns(bStatistics);
- this.mock(_MetadataRequestor).expects("create")
+ const oExpectation = this.mock(_MetadataRequestor).expects("create")
.withExactArgs({"Accept-Language" : "ab-CD"}, "4.0", undefined, bStatistics
? {"sap-client" : "279", "sap-statistics" : true}
- : {"sap-client" : "279"}, undefined)
+ : {"sap-client" : "279"}, undefined, sinon.match.func)
.returns(oMetadataRequestor);
this.mock(ODataMetaModel.prototype).expects("fetchEntityContainer").withExactArgs(true);
this.mock(ODataModel.prototype).expects("initializeSecurityToken").withExactArgs();
@@ -154,6 +154,8 @@ sap.ui.define([
assert.deepEqual(oModel.mPath2DataRequestedCount, {});
assert.deepEqual(oModel.mPath2DataReceivedError, {});
assert.strictEqual(oModel.fnRetryAfter, null);
+ assert.strictEqual(oModel.oRetryAfterPromise, null);
+ assert.strictEqual(oExpectation.args[0][5], oModel.oInterface.getOrCreateRetryAfterPromise);
});
});
@@ -172,7 +174,7 @@ sap.ui.define([
"sap-client" : "279",
"sap-context-token" : "20200716120000",
"sap-language" : "EN"
- }, undefined);
+ }, undefined, sinon.match.func);
this.mock(_Requestor).expects("create")
.withExactArgs(sServiceUrl, sinon.match.object, {"Accept-Language" : "ab-CD"},
{"sap-client" : "279", "sap-context-token" : "n/a"}, "4.0", undefined)
@@ -246,7 +248,7 @@ sap.ui.define([
});
oMetadataRequestorCreateExpectation = this.mock(_MetadataRequestor).expects("create")
.withExactArgs({"Accept-Language" : "ab-CD"}, sODataVersion, undefined,
- sinon.match.object, undefined)
+ sinon.match.object, undefined, sinon.match.func)
.returns({});
// code under test
@@ -445,7 +447,7 @@ sap.ui.define([
this.mock(_MetadataRequestor).expects("create")
.withExactArgs({"Accept-Language" : "ab-CD"}, "4.0",
/*bIngnoreAnnotationsFromMetadata*/undefined, /*mQueryParams*/{},
- /*bWithCredentials*/bWithCredentials);
+ /*bWithCredentials*/bWithCredentials, sinon.match.func);
this.mock(_Requestor).expects("create")
.withExactArgs(sServiceUrl, {
fetchEntityContainer : sinon.match.func,
@@ -456,8 +458,8 @@ sap.ui.define([
getGroupProperty : sinon.match.func,
getMessagesByPath : sinon.match.func,
getOptimisticBatchEnabler : sinon.match.func,
+ getOrCreateRetryAfterPromise : sinon.match.func,
getReporter : sinon.match.func,
- getRetryAfterHandler : sinon.match.func,
isIgnoreETag : sinon.match.func,
onCreateGroup : sinon.match.func,
onHttpResponse : sinon.match.func,
@@ -484,15 +486,6 @@ sap.ui.define([
QUnit.test("Model creates _Requestor, sap-statistics=" + bStatistics, function (assert) {
var oExpectedBind0,
oExpectedBind1,
- oExpectedBind2,
- oExpectedBind3,
- oExpectedBind4,
- oExpectedBind5,
- oExpectedBind6,
- oExpectedBind7,
- oExpectedBind8,
- oExpectedBind9,
- oExpectedBind10,
oExpectedCreate = this.mock(_Requestor).expects("create"),
oModel,
oModelInterface,
@@ -514,8 +507,8 @@ sap.ui.define([
getGroupProperty : "~fnGetGroupProperty~",
getMessagesByPath : "~fnGetMessagesByPath~",
getOptimisticBatchEnabler : "~fnGetOptimisticBatchEnabler~",
+ getOrCreateRetryAfterPromise : "~fnGetOrCreateRetryAfterPromise~",
getReporter : "~fnGetReporter~",
- getRetryAfterHandler : sinon.match.func,
isIgnoreETag : sinon.match.func,
onCreateGroup : sinon.match.func,
onHttpResponse : sinon.match.func,
@@ -534,24 +527,28 @@ sap.ui.define([
.returns("~fnFetchEntityContainer~");
oExpectedBind1 = this.mock(ODataMetaModel.prototype.fetchObject).expects("bind")
.returns("~fnFetchMetadata~");
- oExpectedBind2 = this.mock(ODataModel.prototype.fireDataReceived).expects("bind")
- .returns("~fnFireDataReceived~");
- oExpectedBind3 = this.mock(ODataModel.prototype.fireDataRequested).expects("bind")
- .returns("~fnFireDataRequested~");
- oExpectedBind4 = this.mock(ODataModel.prototype.getGroupProperty).expects("bind")
- .returns("~fnGetGroupProperty~");
- oExpectedBind5 = this.mock(ODataModel.prototype.getMessagesByPath).expects("bind")
- .returns("~fnGetMessagesByPath~");
- oExpectedBind6 = this.mock(ODataModel.prototype.getOptimisticBatchEnabler).expects("bind")
- .returns("~fnGetOptimisticBatchEnabler~");
- oExpectedBind7 = this.mock(ODataModel.prototype.getReporter).expects("bind")
- .returns("~fnGetReporter~");
- oExpectedBind8 = this.mock(ODataModel.prototype.reportTransitionMessages).expects("bind")
- .returns("~fnReportTransitionMessages~");
- oExpectedBind9 = this.mock(ODataModel.prototype.reportStateMessages).expects("bind")
- .returns("~fnReportStateMessages~");
- oExpectedBind10 = this.mock(ODataModel.prototype.reportError).expects("bind")
- .returns("~fnReportError~");
+ const aExpectedBindOnModel = [
+ this.mock(ODataModel.prototype.fireDataReceived).expects("bind")
+ .returns("~fnFireDataReceived~"),
+ this.mock(ODataModel.prototype.fireDataRequested).expects("bind")
+ .returns("~fnFireDataRequested~"),
+ this.mock(ODataModel.prototype.getGroupProperty).expects("bind")
+ .returns("~fnGetGroupProperty~"),
+ this.mock(ODataModel.prototype.getMessagesByPath).expects("bind")
+ .returns("~fnGetMessagesByPath~"),
+ this.mock(ODataModel.prototype.getOptimisticBatchEnabler).expects("bind")
+ .returns("~fnGetOptimisticBatchEnabler~"),
+ this.mock(ODataModel.prototype.getOrCreateRetryAfterPromise).expects("bind")
+ .returns("~fnGetOrCreateRetryAfterPromise~"),
+ this.mock(ODataModel.prototype.getReporter).expects("bind")
+ .returns("~fnGetReporter~"),
+ this.mock(ODataModel.prototype.reportTransitionMessages).expects("bind")
+ .returns("~fnReportTransitionMessages~"),
+ this.mock(ODataModel.prototype.reportStateMessages).expects("bind")
+ .returns("~fnReportStateMessages~"),
+ this.mock(ODataModel.prototype.reportError).expects("bind")
+ .returns("~fnReportError~")
+ ];
// code under test
oModel = this.createModel("?sap-client=123", {}, true);
@@ -560,15 +557,9 @@ sap.ui.define([
assert.strictEqual(oModel.oRequestor, oRequestor);
assert.strictEqual(oExpectedBind0.firstCall.args[0], oModel.oMetaModel);
assert.strictEqual(oExpectedBind1.firstCall.args[0], oModel.oMetaModel);
- assert.strictEqual(oExpectedBind2.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind3.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind4.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind5.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind6.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind7.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind8.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind9.firstCall.args[0], oModel);
- assert.strictEqual(oExpectedBind10.firstCall.args[0], oModel);
+ aExpectedBindOnModel.forEach((oExpectedBind) => {
+ assert.strictEqual(oExpectedBind.firstCall.args[0], oModel);
+ });
oModelInterface = oExpectedCreate.firstCall.args[1];
assert.strictEqual(oModelInterface, oModel.oInterface);
@@ -633,9 +624,6 @@ sap.ui.define([
oModel.setRetryAfterHandler("~fnRetryAfter~");
assert.strictEqual(oModel.fnRetryAfter, "~fnRetryAfter~");
-
- // code under test
- assert.strictEqual(oModelInterface.getRetryAfterHandler(), "~fnRetryAfter~");
});
});
@@ -1233,6 +1221,7 @@ sap.ui.define([
assert.strictEqual(oModel.mHeaders, undefined);
assert.strictEqual(oModel.mMetadataHeaders, undefined);
+ assert.strictEqual(oModel.oRetryAfterPromise, undefined);
});
//*********************************************************************************************
@@ -3582,6 +3571,83 @@ sap.ui.define([
});
});
+ //*********************************************************************************************
+ QUnit.test("getOrCreateRetryAfterPromise: get", function (assert) {
+ const oModel = this.createModel();
+
+ oModel.oRetryAfterPromise = "~oRetryAfterPromise~";
+ oModel.fnRetryAfter = mustBeMocked; // must not be called
+
+ // code under test
+ assert.strictEqual(oModel.getOrCreateRetryAfterPromise("n/a"), "~oRetryAfterPromise~");
+ });
+
+ //*********************************************************************************************
+ QUnit.test("getOrCreateRetryAfterPromise: create, resolves", async function (assert) {
+ const oModel = this.createModel();
+
+ // code under test
+ assert.strictEqual(oModel.getOrCreateRetryAfterPromise(), null, "initially");
+
+ oModel.fnRetryAfter = mustBeMocked;
+
+ // code under test
+ assert.strictEqual(oModel.getOrCreateRetryAfterPromise(), null, "no error, no create");
+
+ const oRetryAfterPromise = Promise.resolve();
+ this.mock(oModel).expects("fnRetryAfter").withExactArgs("~oRetryAfterError~")
+ .returns(oRetryAfterPromise);
+
+ assert.strictEqual(
+ // code under test
+ oModel.getOrCreateRetryAfterPromise("~oRetryAfterError~"),
+ oRetryAfterPromise);
+
+ // code under test
+ assert.strictEqual(oModel.getOrCreateRetryAfterPromise(), oRetryAfterPromise, "get");
+ assert.strictEqual(oModel.getOrCreateRetryAfterPromise("n/a"), oRetryAfterPromise,
+ "no new promise");
+
+ await oRetryAfterPromise;
+
+ // code under test
+ assert.strictEqual(oModel.getOrCreateRetryAfterPromise(), null, "cleaned up");
+ });
+
+ //*********************************************************************************************
+[false, true].forEach((bOwnError) => {
+ const sTitle = "getOrCreateRetryAfterPromise: create, rejects w/ own error = " + bOwnError;
+
+ QUnit.test(sTitle, async function (assert) {
+ const oModel = this.createModel();
+
+ oModel.fnRetryAfter = mustBeMocked;
+ const oError = new Error("Some error message");
+ const oRetryAfterPromise = Promise.reject(oError);
+ const oRetryAfterError = bOwnError ? "~oRetryAfterError~" : oError;
+ this.mock(oModel).expects("fnRetryAfter").withExactArgs(sinon.match.same(oRetryAfterError))
+ .returns(oRetryAfterPromise);
+ this.mock(oModel).expects("reportError").exactly(bOwnError ? 0 : 1)
+ .withExactArgs("Some error message", sClassName, sinon.match.same(oError));
+
+ assert.strictEqual(
+ // code under test
+ oModel.getOrCreateRetryAfterPromise(oRetryAfterError),
+ oRetryAfterPromise);
+
+ await oRetryAfterPromise.catch(() => {});
+
+ // code under test
+ assert.strictEqual(oModel.getOrCreateRetryAfterPromise(), null, "cleaned up");
+
+ if (bOwnError) {
+ assert.strictEqual(oError.$reported, true);
+ } else {
+ assert.notOk("$reported" in oError);
+ }
+ });
+});
+
//*********************************************************************************************
QUnit.test("getKeyPredicate, requestKeyPredicate", function (assert) {
return TestUtils.checkGetAndRequest(this, this.createModel(), assert, "fetchKeyPredicate",
diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.realOData.qunit.js b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.realOData.qunit.js
index 80947bc25d04..8f71404694f1 100644
--- a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.realOData.qunit.js
+++ b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/ODataModel.realOData.qunit.js
@@ -192,6 +192,7 @@ sap.ui.define([
oModelInterface = {
fireSessionTimeout : function () {},
getGroupProperty : defaultGetGroupProperty,
+ getOrCreateRetryAfterPromise : function () {},
isIgnoreETag : function () {},
onCreateGroup : function () {},
onHttpResponse : function () {},
diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_MetadataRequestor.qunit.js b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_MetadataRequestor.qunit.js
index 7ca20063da54..a4484c3c99d8 100644
--- a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_MetadataRequestor.qunit.js
+++ b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_MetadataRequestor.qunit.js
@@ -19,8 +19,13 @@ sap.ui.define([
: {source : "metadata.xml"},
"/sap/opu/odata4/IWBEP/TEA/default/IWBEP/TEA_BUSI/0001/metadata.json"
: {source : "metadata.json"}
+ },
+ fnGetOrCreateRetryAfterPromise = function () {
+ return null;
};
+ function mustBeMocked() { throw new Error("Must be mocked"); }
+
/**
* Creates a mock for jQuery's XHR wrapper.
*
@@ -127,10 +132,9 @@ sap.ui.define([
}
// code under test
- oMetadataRequestor
- = _MetadataRequestor.create(mHeaders, sODataVersion,
- /*bIgnoreAnnotationsFromMetadata*/false, mQueryParams,
- /*bWithCredentials*/true);
+ oMetadataRequestor = _MetadataRequestor.create(mHeaders, sODataVersion,
+ /*bIgnoreAnnotationsFromMetadata*/false, mQueryParams,
+ /*bWithCredentials*/true, fnGetOrCreateRetryAfterPromise);
// code under test
return oMetadataRequestor.read(sUrl).then(function (oResult) {
@@ -150,7 +154,7 @@ sap.ui.define([
});
//*********************************************************************************************
- QUnit.test("read: success with Date, ETag and Last-Modified", function (assert) {
+ QUnit.test("read: success with Date, ETag and Last-Modified", async function (assert) {
var sDate = "Tue, 18 Apr 2017 14:40:29 GMT",
sETag = 'W/"19700101000000.0000000"',
sLastModified = "Fri, 07 Apr 2017 11:21:50 GMT",
@@ -167,10 +171,28 @@ sap.ui.define([
},
oExpectedXml = {},
mHeaders = {},
- oMetadataRequestor = _MetadataRequestor.create(mHeaders, "4.0"),
+ fnResolve,
+ oRetryAfterPromise = new Promise(function (resolve) {
+ fnResolve = resolve;
+ }),
+ oMetadataRequestor = _MetadataRequestor.create(mHeaders, "4.0", undefined, null, false,
+ function () {
+ return oRetryAfterPromise;
+ }),
sUrl = "/~/";
- this.mock(jQuery).expects("ajax")
+ const oJQueryMock = this.mock(jQuery);
+ oJQueryMock.expects("ajax").never();
+
+ // code under test
+ const oPromise = oMetadataRequestor.read(sUrl);
+
+ await new Promise(function (resolve) {
+ setTimeout(resolve, 5);
+ });
+
+ fnResolve();
+ oJQueryMock.expects("ajax")
.withExactArgs(sUrl, {
headers : sinon.match.same(mHeaders),
method : "GET"
@@ -179,9 +201,26 @@ sap.ui.define([
.withExactArgs(sinon.match.same(oExpectedXml), sUrl, undefined)
.returns(oExpectedJson);
+ const oResult = await oPromise;
+
+ assert.deepEqual(oResult, oExpectedResult);
+ });
+
+ //*********************************************************************************************
+ QUnit.test("read: oRetryAfterPromise rejects", function (assert) {
+ const oMetadataRequestor = _MetadataRequestor.create({}, "4.0", undefined, null, false,
+ function () {
+ return Promise.reject("~oError~");
+ });
+ this.mock(jQuery).expects("ajax").never();
+ // Note: if oRetryAfterPromise rejects, "app" is broken and we don't care about other
+ // features of read()
+
// code under test
- return oMetadataRequestor.read(sUrl).then(function (oResult) {
- assert.deepEqual(oResult, oExpectedResult);
+ return oMetadataRequestor.read("n/a").then(function () {
+ assert.ok(false);
+ }, function (oError) {
+ assert.strictEqual(oError, "~oError~");
});
});
@@ -212,7 +251,8 @@ sap.ui.define([
oHelperMock.expects("buildQuery")
.withExactArgs(sinon.match.same(mQueryParams))
.returns(sQuery1);
- oMetadataRequestor = _MetadataRequestor.create(mHeaders, "4.0", true, mQueryParams);
+ oMetadataRequestor = _MetadataRequestor.create(mHeaders, "4.0", true, mQueryParams,
+ false, fnGetOrCreateRetryAfterPromise);
oJQueryMock.expects("ajax")
.withExactArgs(sAnnotationUrl, {
headers : sinon.match.same(mHeaders),
@@ -267,7 +307,8 @@ sap.ui.define([
sLastModified = "Fri, 07 Apr 2017 11:21:50 GMT",
oExpectedXml = {},
mHeaders = {},
- oMetadataRequestor = _MetadataRequestor.create(mHeaders, "4.0"),
+ oMetadataRequestor = _MetadataRequestor.create(mHeaders, "4.0", undefined, null, false,
+ fnGetOrCreateRetryAfterPromise),
sUrl = "/~/";
oJQueryMock.expects("ajax")
@@ -335,31 +376,96 @@ sap.ui.define([
});
//*********************************************************************************************
- QUnit.test("read: failure", function (assert) {
- var jqXHR = {},
- oExpectedError = new Error("404 Not Found"),
- oMetadataRequestor = _MetadataRequestor.create({}, "4.0"),
- sUrl = "/foo/$metadata";
-
+[404, 503].forEach((iStatus) => {
+ QUnit.test("read: failure, status=" + iStatus, function (assert) {
+ const oObject = {
+ getOrCreateRetryAfterPromise : mustBeMocked
+ };
+ const oObjectMock = this.mock(oObject);
+ oObjectMock.expects("getOrCreateRetryAfterPromise").withExactArgs().returns(null); // get
+ const oMetadataRequestor = _MetadataRequestor.create({}, "4.0", false, null, false,
+ oObject.getOrCreateRetryAfterPromise);
+ const jqXHR = {
+ status : iStatus,
+ getResponseHeader : mustBeMocked
+ };
+ this.mock(jqXHR).expects("getResponseHeader").exactly(iStatus === 503 ? 1 : 0)
+ .withExactArgs("Retry-After").returns(""); // "" is a very near miss :-)
this.mock(jQuery).expects("ajax")
.returns(createMock(jqXHR, true)); // true = fail
+ const oExpectedError = new Error("Intentionally failed");
this.mock(_Helper).expects("createError")
.withExactArgs(sinon.match.same(jqXHR), "Could not load metadata")
.returns(oExpectedError);
this.oLogMock.expects("error")
- .withExactArgs("GET " + sUrl, oExpectedError.message,
+ .withExactArgs("GET " + "/foo/$metadata", oExpectedError.message,
"sap.ui.model.odata.v4.lib._MetadataRequestor");
- return oMetadataRequestor.read(sUrl).then(function () {
+ return oMetadataRequestor.read("/foo/$metadata").then(function () {
assert.ok(false);
}, function (oError) {
assert.strictEqual(oError, oExpectedError);
});
});
+});
+
+ //*********************************************************************************************
+[false, true].forEach((bRepeat) => {
+ QUnit.test("read: 503 failure, repeat: " + bRepeat, function (assert) {
+ const oObject = {
+ getOrCreateRetryAfterPromise : mustBeMocked
+ };
+ const oObjectMock = this.mock(oObject);
+ oObjectMock.expects("getOrCreateRetryAfterPromise").withExactArgs().returns(null); // get
+ const oMetadataRequestor = _MetadataRequestor.create({}, "4.0", false, null, false,
+ oObject.getOrCreateRetryAfterPromise);
+ const oJQueryMock = this.mock(jQuery);
+ const jqXHR = {
+ status : 503,
+ getResponseHeader : mustBeMocked
+ };
+ oJQueryMock.expects("ajax").withExactArgs("/foo/$metadata", sinon.match.object)
+ .returns(createMock(jqXHR, true)); // true = fail
+ const oRetryAfterError = new Error("DB migration in progress");
+ this.mock(_Helper).expects("createError")
+ .withExactArgs(sinon.match.same(jqXHR), "Could not load metadata")
+ .returns(oRetryAfterError);
+ this.mock(jqXHR).expects("getResponseHeader").withExactArgs("Retry-After").returns("42");
+ oObjectMock.expects("getOrCreateRetryAfterPromise")
+ .withExactArgs(sinon.match.same(oRetryAfterError)).returns("truthy"); // create
+ const oRetryAfterPromise = bRepeat ? Promise.resolve() : Promise.reject("~oError~");
+ oObjectMock.expects("getOrCreateRetryAfterPromise")
+ .withExactArgs().returns(oRetryAfterPromise); // get
+ const oExpectedJson = {};
+ if (bRepeat) {
+ oJQueryMock.expects("ajax").withExactArgs("/foo/$metadata", sinon.match.object)
+ .returns(createMock("~oData~"));
+ this.mock(_V4MetadataConverter.prototype).expects("convertXMLMetadata")
+ .withExactArgs("~oData~", "/foo/$metadata", false)
+ .returns(oExpectedJson);
+ } else {
+ this.mock(_V4MetadataConverter.prototype).expects("convertXMLMetadata").never();
+ // Note: if oRetryAfterPromise rejects, "app" is broken and we don't care about other
+ // features of read()
+ }
+
+ oRetryAfterPromise.catch(() => {}); // avoid random(?) "global failure"
+
+ // code under test
+ return oMetadataRequestor.read("/foo/$metadata").then(function (oResult) {
+ assert.ok(bRepeat);
+ assert.strictEqual(oResult, oExpectedJson);
+ }, function (oError) {
+ assert.ok(!bRepeat);
+ assert.strictEqual(oError, "~oError~");
+ });
+ });
+});
//*********************************************************************************************
QUnit.test("read: test service", function (assert) {
- var oMetadataRequestor = _MetadataRequestor.create({}, "4.0");
+ var oMetadataRequestor = _MetadataRequestor.create({}, "4.0", false, null, false,
+ fnGetOrCreateRetryAfterPromise);
return Promise.all([
oMetadataRequestor.read(
@@ -372,7 +478,8 @@ sap.ui.define([
//*********************************************************************************************
QUnit.test("read: test service; ignoreAnnotationsFromMetadata", function (assert) {
- var oMetadataRequestor = _MetadataRequestor.create({}, "4.0", true, {});
+ var oMetadataRequestor = _MetadataRequestor.create({}, "4.0", true, {}, false,
+ fnGetOrCreateRetryAfterPromise);
return Promise.all([
oMetadataRequestor.read(
diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_Requestor.qunit.js b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_Requestor.qunit.js
index ee8ccde3e9db..5a655443e7d0 100644
--- a/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_Requestor.qunit.js
+++ b/src/sap.ui.core/test/sap/ui/core/qunit/odata/v4/lib/_Requestor.qunit.js
@@ -22,8 +22,8 @@ sap.ui.define([
fireSessionTimeout : function () {},
getGroupProperty : defaultGetGroupProperty,
getOptimisticBatchEnabler : mustBeMocked,
+ getOrCreateRetryAfterPromise : function () {}, // called too often for mustBeMocked
getReporter : function () {},
- getRetryAfterHandler : function () {},
isIgnoreETag : function () {},
onCreateGroup : function () {},
onHttpResponse : mustBeMocked,
@@ -261,7 +261,6 @@ sap.ui.define([
assert.deepEqual(oRequestor.aLockedGroupLocks, []);
assert.strictEqual(oRequestor.oModelInterface, oModelInterface);
assert.strictEqual(oRequestor.sQueryParams, "?~");
- assert.strictEqual(oRequestor.oRetryAfterPromise, null);
assert.deepEqual(oRequestor.mRunningChangeRequests, {});
assert.strictEqual(oRequestor.oSecurityTokenPromise, null);
assert.strictEqual(oRequestor.iSessionTimer, 0);
@@ -914,17 +913,15 @@ sap.ui.define([
});
//*********************************************************************************************
- // Integrative test simulating 503 "Retry-After" handling: resolve or reject with original or
- // own error
+ // Integrative test simulating 503 "Retry-After" handling: resolve or reject
// 1) Send 2 parallel requests (both supposed to be answered with 503)
// 1a) 1st 503 error response creates "Retry-After" promise
// 1b) 2nd response reuses the promise
// 2) 3rd follow up request reuses also the promise, no request sent
// 3a) Resolving promise repeats all 3 requests
- // 3b) Rejecting promise rejects all 3 requests with the original error or an own error
-[false, /*reject with own error*/null, true].forEach((bResolve) => {
+ // 3b) Rejecting promise rejects all 3 requests
+[false, true].forEach((bResolve) => {
QUnit.test(`sendRequest: 503, "Retry-After" handling, bResolve=${bResolve}`, function (assert) {
- const bOwnError = bResolve === null;
const oRequestor = _Requestor.create("/Service/", oModelInterface);
let fnReject;
let fnResolve;
@@ -933,44 +930,20 @@ sap.ui.define([
fnReject = reject;
});
const oRetryAfterError = {message : "DB migration in progress"};
- function fnRetryAfter(oError) {
- assert.strictEqual(oError, oRetryAfterError);
- return oRetryAfterPromise;
- }
-
- const oOwnError = {};
- function checkError(oError) {
- assert.notOk(bResolve);
- assert.strictEqual(oError, bOwnError ? oOwnError : oRetryAfterError);
- if (bOwnError) {
- assert.strictEqual(oError.$reported, true);
- } else {
- assert.notOk("$reported" in oError);
- }
- }
-
- function checkSuccess() {
- assert.ok(bResolve);
- }
-
const oHelperMock = this.mock(_Helper);
- const o503jqXHR = {
- getResponseHeader() {},
- status : 503
- };
const oJQueryMock = this.mock(jQuery);
- const o503jqXHRMock = this.mock(o503jqXHR);
const oModelInterfaceMock = this.mock(oModelInterface);
+ oModelInterfaceMock.expects("getOrCreateRetryAfterPromise").twice().withExactArgs()
+ .returns(null);
let oSendPromise3;
- this.mock(oRequestor.oModelInterface).expects("reportError")
- .exactly(bResolve === false ? 1 : 0)
- .withExactArgs(oRetryAfterError.message, sClassName,
- sinon.match.same(oRetryAfterError));
oJQueryMock.expects("ajax").withArgs("/Service/First").callsFake(() => {
const jqXHR = new jQuery.Deferred();
setTimeout(async () => {
- assert.strictEqual(oRequestor.oRetryAfterPromise, null);
-
+ const o503jqXHR = {
+ getResponseHeader() {},
+ status : 503
+ };
+ const o503jqXHRMock = this.mock(o503jqXHR);
o503jqXHRMock.expects("getResponseHeader")
.withExactArgs("SAP-ContextId")
.returns("n/a");
@@ -980,13 +953,13 @@ sap.ui.define([
o503jqXHRMock.expects("getResponseHeader")
.withExactArgs("Retry-After")
.returns("42");
- oModelInterfaceMock.expects("getRetryAfterHandler")
- .withExactArgs()
- .twice()
- .returns(fnRetryAfter);
oHelperMock.expects("createError")
.withExactArgs(sinon.match.same(o503jqXHR), "")
.returns(oRetryAfterError);
+ oModelInterfaceMock.expects("getOrCreateRetryAfterPromise")
+ .withExactArgs(sinon.match.same(oRetryAfterError)).returns("truthy"); // create
+ oModelInterfaceMock.expects("getOrCreateRetryAfterPromise").withExactArgs()
+ .returns(oRetryAfterPromise); // get
// (1a)
jqXHR.reject(o503jqXHR);
@@ -997,9 +970,10 @@ sap.ui.define([
// continue regardless of error
}
- assert.strictEqual(oRequestor.oRetryAfterPromise, oRetryAfterPromise);
// register follow up request for oRetryAfterPromise and NOT oSecurityTokenPromise
oRequestor.oSecurityTokenPromise = {};
+ oModelInterfaceMock.expects("getOrCreateRetryAfterPromise").withExactArgs()
+ .returns(oRetryAfterPromise); // get
// code under test (2)
oSendPromise3 = oRequestor.sendRequest("POST", "Third");
@@ -1011,6 +985,11 @@ sap.ui.define([
oJQueryMock.expects("ajax").withArgs("/Service/Second").callsFake(() => {
const jqXHR = new jQuery.Deferred();
setTimeout(async () => {
+ const o503jqXHR = {
+ getResponseHeader() {},
+ status : 503
+ };
+ const o503jqXHRMock = this.mock(o503jqXHR);
o503jqXHRMock.expects("getResponseHeader")
.withExactArgs("SAP-ContextId")
.returns("n/a");
@@ -1020,6 +999,13 @@ sap.ui.define([
o503jqXHRMock.expects("getResponseHeader")
.withExactArgs("Retry-After")
.returns("42");
+ oHelperMock.expects("createError")
+ .withExactArgs(sinon.match.same(o503jqXHR), "")
+ .returns("~oRetryAfterError~");
+ oModelInterfaceMock.expects("getOrCreateRetryAfterPromise")
+ .withExactArgs("~oRetryAfterError~").returns("n/a"); // no create needed
+ oModelInterfaceMock.expects("getOrCreateRetryAfterPromise").withExactArgs()
+ .returns(oRetryAfterPromise); // get same as other parallel request
// (1b)
jqXHR.reject(o503jqXHR);
@@ -1030,15 +1016,10 @@ sap.ui.define([
// continue regardless of error
}
- assert.strictEqual(oRequestor.oRetryAfterPromise, oRetryAfterPromise);
-
if (bResolve) {
oJQueryMock.expects("ajax")
.withArgs("/Service/First")
.callsFake(() => {
- assert.strictEqual(oRequestor.oRetryAfterPromise, null);
- // check that promise is reset only once in case of resolving
- oRequestor.oRetryAfterPromise = "~otherPromise~";
return createMock(assert, {/*oPayload*/}, "OK");
});
oJQueryMock.expects("ajax")
@@ -1059,7 +1040,7 @@ sap.ui.define([
fnResolve();
} else {
// code under test (3b)
- fnReject(bOwnError ? oOwnError : oRetryAfterError);
+ fnReject(oRetryAfterError);
}
}, 0);
@@ -1070,24 +1051,19 @@ sap.ui.define([
const oSendPromise1 = oRequestor.sendRequest("GET", "First");
const oSendPromise2 = oRequestor.sendRequest("GET", "Second");
+ const checkError = (oError) => {
+ assert.notOk(bResolve);
+ assert.strictEqual(oError, oRetryAfterError);
+ };
+ const checkSuccess = () => {
+ assert.ok(bResolve);
+ };
+
return Promise.all([
- oSendPromise1.then(checkSuccess, (oError) => {
- assert.strictEqual(oRequestor.oRetryAfterPromise, null);
- // check that promise is reset only once in case of rejecting
- oRequestor.oRetryAfterPromise = "~otherPromise~";
- checkError(oError);
- }),
- oSendPromise2.then(checkSuccess, checkError),
- oRetryAfterPromise.then(checkSuccess, function (oError) {
- assert.notOk(bResolve);
- assert.strictEqual(oError, bOwnError ? oOwnError : oRetryAfterError);
- // must not check $reported before caught and set in productive code
- })
+ oSendPromise1.then(checkSuccess, checkError),
+ oSendPromise2.then(checkSuccess, checkError)
]).then(function () {
return oSendPromise3.then(checkSuccess, checkError);
- }).then(function () {
- assert.strictEqual(oRequestor.oRetryAfterPromise, "~otherPromise~");
- return oRetryAfterPromise.then(checkSuccess, checkError);
});
});
});
@@ -1111,11 +1087,15 @@ sap.ui.define([
o503jqXHRMock.expects("getResponseHeader")
.withExactArgs("Retry-After")
.returns(sRetryAfter);
- this.mock(oModelInterface).expects("getRetryAfterHandler")
- .withExactArgs()
+ const oHelperMock = this.mock(_Helper);
+ oHelperMock.expects("createError").exactly(sRetryAfter ? 1 : 0)
+ .withExactArgs(sinon.match.same(o503jqXHR), "")
+ .returns("n/a");
+ this.mock(oModelInterface).expects("getOrCreateRetryAfterPromise")
.exactly(sRetryAfter ? 1 : 0)
+ .withExactArgs("n/a")
.returns(null);
- this.mock(_Helper).expects("createError")
+ oHelperMock.expects("createError")
.withExactArgs(sinon.match.same(o503jqXHR), "Communication error",
"/Service/Foo", undefined)
.returns("~oError~");