diff --git a/src/js/collections/DataPackage.js b/src/js/collections/DataPackage.js index ae1e527c1..a2f26ee14 100644 --- a/src/js/collections/DataPackage.js +++ b/src/js/collections/DataPackage.js @@ -573,67 +573,94 @@ define([ }, /** - * Overload fetch calls for a DataPackage - * @param {object} [options] - Optional options for this fetch that get - * sent with the XHR request - * @property {boolean} fetchModels - If false, this fetch will not fetch - * each model in the collection. It will only get the resource map object. - * @property {boolean} fromIndex - If true, the collection will be fetched - * from Solr rather than fetching the system metadata of each model. - * Useful when you only need to retrieve limited information about each - * package member. Set query-specific parameters on the `solrResults` - * SolrResults set on this collection. - * @returns {jqXHR} The jQuery XMLHttpRequest for the request + * Overload fetch calls for a DataPackage + * + * This fetch function will fetch the resource map RDF XML for this package + * + * + Example 1: `this.fetch();` + * + Example 2: `this.fetch({fetchModels: false});` + * + Example 3: `this.fetch({fromIndex: true});` + * + Example 4: + * ``` + * this.fetch() + * .then(function() { + * console.log("Fetch complete!"); + * }) + * .catch(function() { + * console.log("Fetch failed!"); + * }); + * ``` + * + * @param {object} [options] - Optional options for this fetch that get sent with the XHR request + * @property {boolean} fetchModels - If false, this fetch will not fetch + * each model in the collection. It will only get the resource map object. + * @property {boolean} fromIndex - If true, the collection will be fetched from Solr rather than + * fetching the system metadata of each model. Useful when you only need to retrieve limited information about + * each package member. Set query-specific parameters on the `solrResults` SolrResults set on this collection. + * @return {Promise} A promise that resolves when the fetch is complete */ - fetch(options = {}) { - // Fetch the system metadata for this resource map - this.packageModel.fetch(); - - const fetchOptions = { dataType: "text", ...options }; - - // If the fetchModels property is set to false, - if (fetchOptions.fetchModels === false) { - // Save the property to the Collection itself so it is accessible in - // other functions - this.fetchModels = false; - // Remove the property from the options Object since we don't want to - // send it with the XHR - delete fetchOptions.fetchModels; - this.once("reset", this.triggerComplete); - } - // If the fetchFromIndex property is set to true - else if (fetchOptions.fromIndex) { - this.fetchFromIndex(); - return null; - } + fetch(options) { + return new Promise((resolve, reject) => { + // Fetch the system metadata for this resource map + this.packageModel.fetch(); + + if (typeof options === "object") { + // If the fetchModels property is set to false, + if (options.fetchModels === false) { + // Save the property to the Collection itself so it is accessible in other functions + this.fetchModels = false; + // Remove the property from the options Object since we don't want to send it with the XHR + delete options.fetchModels; + this.once("reset", () => { + this.triggerComplete(); + resolve(); + }); + } + // If the fetchFromIndex property is set to true + else if (options.fromIndex) { + this.fetchFromIndex(); + resolve(); + return; + } + } - const thisPackage = this; + // Set some custom fetch options + const fetchOptions = _.extend({ dataType: "text" }, options); - // Function to retry fetching with user login details if the initial - // fetch fails eslint-disable-next-line func-names - const retryFetch = () => { - // Add the authorization options - const authFetchOptions = _.extend( - fetchOptions, - MetacatUI.appUserModel.createAjaxSettings(), - ); + const thisPackage = this; + + // Function to retry fetching with user login details if the initial fetch fails + const retryFetch = function () { + // Add the authorization options + const authFetchOptions = _.extend( + fetchOptions, + MetacatUI.appUserModel.createAjaxSettings(), + ); + + // Fetch the resource map RDF XML with user login details + return Backbone.Collection.prototype.fetch + .call(thisPackage, authFetchOptions) + .fail(() => { + // trigger failure() + console.log("Fetch failed"); + + thisPackage.trigger("fetchFailed", thisPackage); + reject(); + }); + }; - // Fetch the resource map RDF XML with user login details - return Backbone.Collection.prototype.fetch - .call(thisPackage, authFetchOptions) + // Fetch the resource map RDF XML + Backbone.Collection.prototype.fetch + .call(this, fetchOptions) + .done(() => resolve()) .fail(() => { - // TODO: Handle the fetch failure! - thisPackage.trigger("fetchFailed", thisPackage); + console.log("Fetch failed. Retrying with user login details..."); + // If the initial fetch fails, retry with user login details + retryFetch() + .done(() => resolve()) + .fail(() => reject()); }); - }; - - // Fetch the resource map RDF XML - return Backbone.Collection.prototype.fetch - .call(this, fetchOptions) - .fail(() => - // If the initial fetch fails, retry with user login details - retryFetch(), - ); + }); }, /** diff --git a/test/js/specs/unit/collections/DataPackage.spec.js b/test/js/specs/unit/collections/DataPackage.spec.js index 9ee585c98..2285f5329 100644 --- a/test/js/specs/unit/collections/DataPackage.spec.js +++ b/test/js/specs/unit/collections/DataPackage.spec.js @@ -6,12 +6,42 @@ define([ describe("DataPackage Test Suite", function () { let dataPackage; + let originalFetch, + originalTriggerComplete, + originalFetchFromIndex, + originalCreateAjaxSettings; + let fetchCallCount, + triggerCompleteCallCount, + fetchFromIndexCallCount, + createAjaxSettingsCallCount; beforeEach(function () { dataPackage = new DataPackage(); + originalFetch = dataPackage.packageModel.fetch; + originalTriggerComplete = dataPackage.triggerComplete; + originalFetchFromIndex = dataPackage.fetchFromIndex; + originalCreateAjaxSettings = MetacatUI.appUserModel.createAjaxSettings; + + fetchCallCount = 0; + triggerCompleteCallCount = 0; + fetchFromIndexCallCount = 0; + createAjaxSettingsCallCount = 0; + + dataPackage.triggerComplete = function () { + triggerCompleteCallCount++; + }; + dataPackage.fetchFromIndex = function () { + fetchFromIndexCallCount++; + }; + MetacatUI.appUserModel.createAjaxSettings = function () { + createAjaxSettingsCallCount++; + }; }); afterEach(function () { + dataPackage.triggerComplete = originalTriggerComplete; + dataPackage.fetchFromIndex = originalFetchFromIndex; + MetacatUI.appUserModel.createAjaxSettings = originalCreateAjaxSettings; dataPackage = undefined; }); @@ -133,5 +163,115 @@ define([ }, 1000); }); }); + + describe("fetch", function () { + let originalFetch; + + // Save the original fetch method and mock it to return a resolved promise before each test + beforeEach(function () { + originalFetch = Backbone.Collection.prototype.fetch; + Backbone.Collection.prototype.fetch = function (options) { + // Mocked fetch method that returns a resolved promise + var deferred = $.Deferred(); + deferred.resolve(); + return deferred.promise(); + }; + }); + + // Restore the original fetch method after each test + afterEach(function () { + Backbone.Collection.prototype.fetch = originalFetch; + }); + + // Test that packageModel.fetch is called with no options + it("should call packageModel.fetch with no options", function (done) { + dataPackage.packageModel.fetch = function () { + // Mocked fetch method that increments fetchCallCount and returns a resolved promise + fetchCallCount++; + return Promise.resolve(); + }; + + dataPackage + .fetch() + .then(function () { + expect(fetchCallCount).to.equal(1); + done(); // Call done() to finish the test + }) + .catch(function () { + done(new Error("Expected fetch to succeed")); + }); + }); + + // Test that packageModel.fetch is called with fetchModels: false + it("should call packageModel.fetch with fetchModels: false", function (done) { + dataPackage.packageModel.fetch = function () { + // Mocked fetch method that increments fetchCallCount and returns a resolved promise + fetchCallCount++; + return Promise.resolve(); + }; + + dataPackage + .fetch({ fetchModels: false }) + .then(function () { + expect(fetchCallCount).to.equal(1); + expect(fetchFromIndexCallCount).to.equal(0); + done(); // Call done() to finish the test + }) + .catch(function () { + done(new Error("Expected fetch to succeed")); + }); + }); + + // Test that fetchFromIndex is called with fromIndex: true + it("should call fetchFromIndex with fromIndex: true", function (done) { + dataPackage + .fetch({ fromIndex: true }) + .then(function () { + expect(fetchFromIndexCallCount).to.equal(1); + done(); // Call done() to finish the test + }) + .catch(function () { + done(new Error("Expected fetch to succeed")); + }); + }); + + // Test that createAjaxSettings is called + it("should call createAjaxSettings", function (done) { + dataPackage + .fetch() + .then(function () { + expect(createAjaxSettingsCallCount).to.equal(1); + done(); // Call done() to finish the test + }) + .catch(function () { + done(new Error("Expected fetch to succeed")); + }); + }); + + // Test that the fetch method handles done and fail callbacks correctly + it("should handle done and fail callbacks", function (done) { + dataPackage.packageModel.fetch = function () { + // Mocked fetch method that increments fetchCallCount and returns a rejected promise + fetchCallCount++; + return Promise.reject(); + }; + Backbone.Collection.prototype.fetch = function (options) { + // Mocked fetch method that returns a rejected promise + var deferred = $.Deferred(); + deferred.reject(); + return deferred.promise(); + }; + dataPackage + .fetch() + .then(function () { + done(new Error("Expected fetch to fail")); + }) + .catch(function () { + expect(fetchCallCount).to.equal(1); + expect(fetchFromIndexCallCount).to.equal(0); + done(); + }); + }); + }); }); });