diff --git a/lib/api_client/execute_request.js b/lib/api_client/execute_request.js index 9c0418d9..cd53969d 100644 --- a/lib/api_client/execute_request.js +++ b/lib/api_client/execute_request.js @@ -2,7 +2,6 @@ const config = require("../config"); const https = /^http:/.test(config().upload_prefix) ? require('http') : require('https'); const querystring = require("querystring"); -const Q = require('q'); const url = require('url'); const utils = require("../utils"); const ensureOption = require('../utils/ensureOption').defaults(config()); @@ -13,7 +12,7 @@ const agent = config.api_proxy ? new https.Agent(config.api_proxy) : null; function execute_request(method, params, auth, api_url, callback, options = {}) { method = method.toUpperCase(); - const deferred = Q.defer(); + const deferred = utils.deferredPromise(); let query_params, handle_response; // declare to user later let key = auth.key; diff --git a/lib/uploader.js b/lib/uploader.js index 93318f02..627267c9 100644 --- a/lib/uploader.js +++ b/lib/uploader.js @@ -1,6 +1,5 @@ const fs = require('fs'); const { extname, basename } = require('path'); -const Q = require('q'); const Writable = require("stream").Writable; const urlLib = require('url'); @@ -466,7 +465,7 @@ function call_api(action, callback, options, get_params) { const USE_PROMISES = !options.disable_promises; - let deferred = Q.defer(); + const deferred = utils.deferredPromise(); if (options == null) { options = {}; } diff --git a/lib/utils/index.js b/lib/utils/index.js index e775a14a..8802f498 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -92,6 +92,7 @@ const { SUPPORTED_SIGNATURE_ALGORITHMS, DEFAULT_SIGNATURE_ALGORITHM } = require('./consts'); +const applyQCompat = require("./qPolyfill"); function textStyle(layer) { let keywords = []; @@ -1652,6 +1653,23 @@ function jsonArrayParam(data, modifier) { */ exports.NOP = function () { }; + +function deferredPromise() { + let resolve, reject + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + applyQCompat(promise); + return { + resolve, + reject, + promise + }; +} + +exports.deferredPromise = deferredPromise; + exports.generate_auth_token = generate_auth_token; exports.getUserAgent = getUserAgent; exports.build_upload_params = build_upload_params; diff --git a/lib/utils/qPolyfill.js b/lib/utils/qPolyfill.js new file mode 100644 index 00000000..126a0300 --- /dev/null +++ b/lib/utils/qPolyfill.js @@ -0,0 +1,131 @@ +const scheduleCompatCallback = typeof setImmediate === 'function' + ? (fn) => setImmediate(fn) + : (fn) => setTimeout(fn, 0); + +function qFinally(onFinally) { + const handler = typeof onFinally === 'function' ? onFinally : () => onFinally; + const PromiseCtor = typeof this.constructor === 'function' && typeof this.constructor.resolve === 'function' + ? this.constructor + : Promise; + return this.then( + (value) => PromiseCtor.resolve(handler()).then(() => value), + (reason) => PromiseCtor.resolve(handler()).then(() => { + throw reason; + }) + ); +} + +function qFin(handler) { + return this.finally(handler); +} + +function qDone(onFulfilled, onRejected) { + this.then(onFulfilled, onRejected).catch((err) => { + scheduleCompatCallback(() => { + throw err; + }); + }); +} + +function qNodeify(callback) { + if (typeof callback !== 'function') { + return this; + } + + this.then( + (value) => scheduleCompatCallback(() => callback(null, value)), + (error) => scheduleCompatCallback(() => callback(error)) + ); + + return this; +} + +function qFail(onRejected) { + return this.catch(onRejected); +} + +function qProgress() { + return this; +} + +function qSpread(onFulfilled, onRejected) { + return this.then( + (values) => { + if (typeof onFulfilled !== 'function') { + return values; + } + if (Array.isArray(values)) { + // eslint-disable-next-line prefer-spread + return onFulfilled.apply(void 0, values); + } + return onFulfilled(values); + }, + onRejected + ); +} + +function applyQCompat(promise) { + if (!promise || (typeof promise !== 'object' && typeof promise !== 'function')) { + return promise; + } + + if (promise.__cloudinaryQCompatApplied) { + return promise; + } + + Object.defineProperty(promise, '__cloudinaryQCompatApplied', { + value: true, + enumerable: false + }); + + const nativeThen = promise.then; + if (typeof nativeThen === 'function') { + promise.then = function (...args) { + return applyQCompat(nativeThen.apply(this, args)); + }; + } + + const nativeCatch = promise.catch; + if (typeof nativeCatch === 'function') { + promise.catch = function (...args) { + return applyQCompat(nativeCatch.apply(this, args)); + }; + } + + const nativeFinally = promise.finally; + if (typeof nativeFinally === 'function') { + promise.finally = function (...args) { + return applyQCompat(nativeFinally.apply(this, args)); + }; + } else { + promise.finally = qFinally; + } + + if (typeof promise.fin !== 'function') { + promise.fin = qFin; + } + + if (typeof promise.done !== 'function') { + promise.done = qDone; + } + + if (typeof promise.nodeify !== 'function') { + promise.nodeify = qNodeify; + } + + if (typeof promise.fail !== 'function') { + promise.fail = qFail; + } + + if (typeof promise.progress !== 'function') { + promise.progress = qProgress; + } + + if (typeof promise.spread !== 'function') { + promise.spread = qSpread; + } + + return promise; +} + +module.exports = applyQCompat; diff --git a/package.json b/package.json index 4d505c8c..5179e07c 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ }, "main": "cloudinary.js", "dependencies": { - "lodash": "^4.17.21", - "q": "^1.5.1" + "lodash": "^4.17.21" }, "devDependencies": { "@types/expect.js": "^0.3.29", diff --git a/test/integration/api/admin/api_spec.js b/test/integration/api/admin/api_spec.js index 28c9ad84..dc456e1c 100644 --- a/test/integration/api/admin/api_spec.js +++ b/test/integration/api/admin/api_spec.js @@ -4,7 +4,6 @@ const formatDate = require("date-fns").format; const subDate = require("date-fns").sub; const https = require('https'); const ClientRequest = require('_http_client').ClientRequest; -const Q = require('q'); const cloudinary = require("../../../../cloudinary"); const helper = require("../../../spechelper"); const describe = require('../../../testUtils/suite'); @@ -15,11 +14,12 @@ const ADDON_OCR = helper.ADDON_OCR; const callReusableTest = require('../../../testUtils/reusableTests/reusableTests').callReusableTest; const testConstants = require('../../../testUtils/testConstants'); const retry = require('../../../testUtils/helpers/retry'); -const {shouldTestFeature} = require("../../../spechelper"); +const { shouldTestFeature } = require("../../../spechelper"); const API_V2 = cloudinary.v2.api; const DYNAMIC_FOLDERS = helper.DYNAMIC_FOLDERS; const assert = require('assert'); -const {only} = require("../../../../lib/utils"); +const { only } = require("../../../../lib/utils"); +const allSettled = require('../../../testUtils/helpers/allSettled'); const { TIMEOUT, @@ -80,7 +80,7 @@ const METADATA_EXTERNAL_ID = "metadata_external_id_" + TEST_TAG; const METADATA_DEFAULT_VALUE = "metadata_default_value_" + TEST_TAG; -function getAllTags({resources}) { +function getAllTags({ resources }) { return resources .map(e => e.tags) .reduce(((a, b) => a.concat(b)), []); @@ -103,7 +103,7 @@ describe("api", function () { default_value: METADATA_DEFAULT_VALUE }); - await Q.all([ + await Promise.all([ uploadImage({ public_id: PUBLIC_ID, tags: UPLOAD_TAGS, @@ -139,7 +139,7 @@ describe("api", function () { if (!(config.api_key && config.api_secret)) { expect().fail("Missing key and secret. Please set CLOUDINARY_URL."); } - return Q.allSettled([ + return allSettled([ cloudinary.v2.api.delete_metadata_field(METADATA_EXTERNAL_ID), cloudinary.v2.api.delete_resources_by_tag(TEST_TAG), cloudinary.v2.api.delete_upload_preset(API_TEST_UPLOAD_PRESET1), @@ -256,7 +256,7 @@ describe("api", function () { return uploadImage({ tags: UPLOAD_TAGS }).then( - ({public_id}) => cloudinary.v2.api.resources({type: "upload"}) + ({ public_id }) => cloudinary.v2.api.resources({ type: "upload" }) .then(result => [public_id, result]) .then(([resources_public_id, result]) => { let resource = findByAttr(result.resources, "public_id", resources_public_id); @@ -305,7 +305,7 @@ describe("api", function () { }); }); it("should allow get resource details by asset id", async () => { - const {asset_id} = await uploadImage({tags: TEST_TAG}) + const { asset_id } = await uploadImage({ tags: TEST_TAG }) const resource = await API_V2.resource_by_asset_id(asset_id) expect(resource).not.to.be.empty(); expect(resource.asset_id).to.equal(asset_id); @@ -315,7 +315,7 @@ describe("api", function () { expect(resource).not.to.have.property('faces'); }); it("should allow get resource details by asset id including explicitly requested properties", async () => { - const {asset_id} = await uploadImage({tags: TEST_TAG}) + const { asset_id } = await uploadImage({ tags: TEST_TAG }) const resource = await API_V2.resource_by_asset_id(asset_id, { colors: true, faces: true, @@ -331,10 +331,10 @@ describe("api", function () { }); it('should allow listing resources by asset ids', async () => { this.timeout(TIMEOUT.MEDIUM); - const uploads = await Promise.all([uploadImage({tags: TEST_TAG}), uploadImage({tags: TEST_TAG})]); + const uploads = await Promise.all([uploadImage({ tags: TEST_TAG }), uploadImage({ tags: TEST_TAG })]); const assetIds = uploads.map(item => item.asset_id); const publicIds = uploads.map(item => item.public_id); - const {resources} = await API_V2.resources_by_asset_ids(assetIds); + const { resources } = await API_V2.resources_by_asset_ids(assetIds); expect(resources).not.to.be.empty(); expect(resources.length).to.eql(2); expect(publicIds).to.contain(resources[0].public_id); @@ -351,9 +351,9 @@ describe("api", function () { expect(result.resources.map(e => e.context.custom.key)).to.contain("value"); }); }); - it("should allow listing resources specifying direction", function () { + it("should allow listing resources specifying direction", async function () { this.timeout(TIMEOUT.LONG); - Q.all( + await Promise.all([ cloudinary.v2.api.resources_by_tag(TEST_TAG, { type: "upload", max_results: 500, @@ -364,15 +364,15 @@ describe("api", function () { max_results: 500, direction: "desc" }) - ).then(([resultAsc, resultDesc]) => [ + ]).then(([resultAsc, resultDesc]) => [ resultAsc.resources.map(r => r.public_id), resultDesc.resources.map(r => r.public_id) ]).then(([asc, desc]) => expect(asc.reverse()).to.eql(desc)); }); it("should allow listing resources by start_at", function () { let start_at = new Date().toString(); - helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.resources({ + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.resources({ type: "upload", start_at: start_at, direction: "asc" @@ -387,7 +387,7 @@ describe("api", function () { return uploadImage({ tags: UPLOAD_TAGS, eager: [EXPLICIT_TRANSFORMATION] - }).then(({public_id}) => cloudinary.v2.api.resource(public_id) + }).then(({ public_id }) => cloudinary.v2.api.resource(public_id) .then(resource => [public_id, resource])) .then(([public_id, resource]) => { expect(resource).not.to.eql(void 0); @@ -411,9 +411,9 @@ describe("api", function () { }); describe("derived pagination", function () { it("should send the derived_next_cursor to the server", function () { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.resource(PUBLIC_ID, {derived_next_cursor: 'aaa'}); - return sinon.assert.calledWith( + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.resource(PUBLIC_ID, { derived_next_cursor: 'aaa' }); + sinon.assert.calledWith( requestSpy, sinon.match(sinon.match({ query: sinon.match('derived_next_cursor=aaa') }, 'derived_next_cursor=aaa'))); @@ -421,9 +421,9 @@ describe("api", function () { }); }); it("should send `accessibility_analysis` param to the server", function () { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.resource(PUBLIC_ID, {accessibility_analysis: true}); - return sinon.assert.calledWith(requestSpy, sinon.match({ + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.resource(PUBLIC_ID, { accessibility_analysis: true }); + sinon.assert.calledWith(requestSpy, sinon.match({ query: sinon.match(helper.apiParamMatcher("accessibility_analysis", "true")) })); }); @@ -433,25 +433,25 @@ describe("api", function () { const expectedKeys = ['public_id', 'asset_id', 'folder', 'tags'].sort(); it('should allow listing', async () => { - const {resources} = await cloudinary.v2.api.resources({fields: ['tags']}) + const { resources } = await cloudinary.v2.api.resources({ fields: ['tags'] }) const actualKeys = Object.keys(resources[0]); assert.deepStrictEqual(actualKeys.sort(), expectedKeys); }); it('should allow listing by public_ids', async () => { - const {resources} = await cloudinary.v2.api.resources_by_ids([PUBLIC_ID], {fields: ['tags']}) + const { resources } = await cloudinary.v2.api.resources_by_ids([PUBLIC_ID], { fields: ['tags'] }) const actualKeys = Object.keys(resources[0]); assert.deepStrictEqual(actualKeys.sort(), expectedKeys); }); it('should allow listing by tag', async () => { - const {resources} = await cloudinary.v2.api.resources_by_tag(TEST_TAG, {fields: ['tags']}) + const { resources } = await cloudinary.v2.api.resources_by_tag(TEST_TAG, { fields: ['tags'] }) const actualKeys = Object.keys(resources[0]); assert.deepStrictEqual(actualKeys.sort(), expectedKeys); }); it('should allow listing by context', async () => { - const {resources} = await cloudinary.v2.api.resources_by_context(contextKey, "test", {fields: ['tags']}) + const { resources } = await cloudinary.v2.api.resources_by_context(contextKey, "test", { fields: ['tags'] }) const actualKeys = Object.keys(resources[0]); assert.deepStrictEqual(actualKeys.sort(), expectedKeys); }); @@ -461,14 +461,14 @@ describe("api", function () { moderation: 'manual', tags: [TEST_TAG] }); - const {resources} = await cloudinary.v2.api.resources_by_moderation('manual', 'pending', {fields: ['tags']}) + const { resources } = await cloudinary.v2.api.resources_by_moderation('manual', 'pending', { fields: ['tags'] }) const actualKeys = Object.keys(resources[0]); assert.deepStrictEqual(actualKeys.sort(), expectedKeys); }); it('should allow listing by asset_ids', async () => { - const {asset_id} = await uploadImage(); - const {resources} = await cloudinary.v2.api.resources_by_asset_ids([asset_id], {fields: ['tags']}) + const { asset_id } = await uploadImage(); + const { resources } = await cloudinary.v2.api.resources_by_asset_ids([asset_id], { fields: ['tags'] }) const actualKeys = Object.keys(resources[0]); assert.deepStrictEqual(actualKeys.sort(), expectedKeys); }); @@ -490,14 +490,14 @@ describe("api", function () { }); }); it("should return the asset details together with all of its backed up versions when versions is true", function () { - return cloudinary.v2.api.resource(publicId, {versions: true}) + return cloudinary.v2.api.resource(publicId, { versions: true }) .then((resource) => { expect(resource.versions).to.be.an('array'); }); }); it("should return the asset details together without backed up versions when versions is false", function () { - return cloudinary.v2.api.resource(publicId, {versions: false}) + return cloudinary.v2.api.resource(publicId, { versions: false }) .then((resource) => { expect(resource.versions).to.be(undefined); }); @@ -515,7 +515,7 @@ describe("api", function () { } ] }).then(wait(2000)).then( - ({public_id}) => cloudinary.v2.api.resource(public_id) + ({ public_id }) => cloudinary.v2.api.resource(public_id) .then(resource => [public_id, resource]) ).then(([public_id, resource]) => { expect(resource).not.to.eql(void 0); @@ -533,7 +533,7 @@ describe("api", function () { }); it("should allow deleting derived resources by transformations", function () { this.timeout(TIMEOUT.LARGE); - return Q.all([ + return Promise.all([ uploadImage({ public_id: PUBLIC_ID_1, tags: UPLOAD_TAGS, @@ -578,7 +578,7 @@ describe("api", function () { () => cloudinary.v2.api.resource(PUBLIC_ID_3) ).then(() => { expect().fail(); - }).catch(function ({error}) { + }).catch(function ({ error }) { expect(error).to.be.an(Object); expect(error.http_code).to.eql(404); }); @@ -592,13 +592,13 @@ describe("api", function () { () => cloudinary.v2.api.resource(PUBLIC_ID_3) ).then(function (resource) { expect(resource).not.to.eql(void 0); - console.log(resource); + // console.log(resource); return cloudinary.v2.api.delete_resources_by_asset_ids([resource.asset_id]); }).then( () => cloudinary.v2.api.resource(PUBLIC_ID_3) ).then(() => { expect().fail(); - }).catch(function ({error}) { + }).catch(function ({ error }) { expect(error).to.be.an(Object); expect(error.http_code).to.eql(404); }); @@ -619,7 +619,7 @@ describe("api", function () { () => cloudinary.v2.api.resource("api_test_by_prefix") ).then( () => expect().fail() - ).catch(function ({error}) { + ).catch(function ({ error }) { expect(error).to.be.an(Object); expect(error.http_code).to.eql(404); }); @@ -642,7 +642,7 @@ describe("api", function () { () => cloudinary.v2.api.resource(PUBLIC_ID_4) ).then( () => expect().fail() - ).catch(({error}) => { + ).catch(({ error }) => { expect(error).to.be.an(Object); expect(error.http_code).to.eql(404); }); @@ -692,14 +692,11 @@ describe("api", function () { callReusableTest("a list with a cursor", cloudinary.v2.api.transformations); transformationName = "api_test_transformation3" + UNIQUE_JOB_SUFFIX_ID; after(function () { - return Q.allSettled( - [ - cloudinary.v2.api.delete_transformation(transformationName), - cloudinary.v2.api.delete_transformation(NAMED_TRANSFORMATION), - cloudinary.v2.api.delete_transformation(NAMED_TRANSFORMATION2) - ] - ).finally(function () { - }); + return allSettled([ + cloudinary.v2.api.delete_transformation(transformationName), + cloudinary.v2.api.delete_transformation(NAMED_TRANSFORMATION), + cloudinary.v2.api.delete_transformation(NAMED_TRANSFORMATION2) + ]); }); it("should allow listing transformations", function () { this.timeout(TIMEOUT.MEDIUM); @@ -782,11 +779,11 @@ describe("api", function () { }); }); it("should allow listing of named transformations", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.api.transformations({ + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.api.transformations({ named: true }); - return sinon.assert.calledWith(requestSpy, sinon.match({ + sinon.assert.calledWith(requestSpy, sinon.match({ query: sinon.match('named=true') }, "named=true")); }); @@ -819,7 +816,7 @@ describe("api", function () { return cloudinary.v2.api.delete_transformation(NAMED_TRANSFORMATION) .then(() => cloudinary.v2.api.transformation(NAMED_TRANSFORMATION)) .then(() => expect().fail()) - .catch(({error}) => expect(error.http_code).to.eql(404)); + .catch(({ error }) => expect(error.http_code).to.eql(404)); }); }); it("should allow deleting implicit transformation", function () { @@ -831,49 +828,49 @@ describe("api", function () { () => cloudinary.v2.api.transformation(EXPLICIT_TRANSFORMATION_NAME) ).then( () => expect().fail() - ).catch(({error}) => expect(error.http_code).to.eql(404)); + ).catch(({ error }) => expect(error.http_code).to.eql(404)); }); }); describe("upload_preset", function () { callReusableTest("a list with a cursor", cloudinary.v2.api.upload_presets); it("should allow listing upload_presets", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.api.upload_presets(); - return sinon.assert.calledWith(requestSpy, sinon.match({ + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.api.upload_presets(); + sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(/.*\/upload_presets$/) }, "upload_presets")); }); }); it("should allow getting a single upload_preset", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.api.upload_preset(API_TEST_UPLOAD_PRESET1); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.api.upload_preset(API_TEST_UPLOAD_PRESET1).catch(helper.ignoreApiFailure); var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET1 + "$"; - return sinon.assert.calledWith(requestSpy, sinon.match({ + sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("GET") })); }); }); it("should allow deleting upload_presets", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.api.delete_upload_preset(API_TEST_UPLOAD_PRESET2); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.api.delete_upload_preset(API_TEST_UPLOAD_PRESET2).catch(helper.ignoreApiFailure); var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET2 + "$"; - return sinon.assert.calledWith(requestSpy, sinon.match({ + sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("DELETE") })) }); }); it("should allow updating upload_presets", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.api.update_upload_preset(API_TEST_UPLOAD_PRESET3, + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.api.update_upload_preset(API_TEST_UPLOAD_PRESET3, { colors: true, unsigned: true, disallow_public_id: true, live: true, eval: TEST_EVAL_STR - }); + }).catch(helper.ignoreApiFailure); var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET3 + "$"; sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -887,15 +884,15 @@ describe("api", function () { }); }); it("should allow creating upload_presets", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.api.create_upload_preset({ + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.api.create_upload_preset({ folder: "upload_folder", unsigned: true, tags: UPLOAD_TAGS, live: true, eval: TEST_EVAL_STR }).then((preset) => { - cloudinary.v2.api.delete_upload_preset(preset.name).catch((err) => { + return cloudinary.v2.api.delete_upload_preset(preset.name).catch((err) => { console.log(err); // we don't fail the test if the delete fails }); @@ -916,8 +913,8 @@ describe("api", function () { }); }); it("should return usage values for a specific date", function () { - const yesterday = formatDate(subDate(new Date(), {days: 1}), "dd-MM-yyyy"); - return cloudinary.v2.api.usage({date: yesterday}) + const twoDaysAgo = formatDate(subDate(new Date(), { days: 2 }), "dd-MM-yyyy"); + return cloudinary.v2.api.usage({ date: twoDaysAgo }) .then(usage => { expect(usage).to.be.an("object"); expect(usage).to.have.keys("plan", "last_updated", "transformations", "objects", "bandwidth", "storage", "requests", "resources", "derived_resources", "media_limits"); @@ -929,11 +926,11 @@ describe("api", function () { callReusableTest("accepts next_cursor", cloudinary.v2.api.delete_all_resources); describe("keep_original: yes", function () { it("should allow deleting all derived resources", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { let options = { keep_original: true }; - cloudinary.v2.api.delete_all_resources(options); + await cloudinary.v2.api.delete_all_resources(options).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match(arg => new RegExp("/resources/image/upload$").test(arg.pathname), "/resources/image/upload")); sinon.assert.calledWith(requestSpy, sinon.match(arg => arg.method === "DELETE", "DELETE")); sinon.assert.calledWith(writeSpy, sinon.match(helper.apiParamMatcher('keep_original', 'true'), "keep_original=true")); @@ -978,7 +975,7 @@ describe("api", function () { expect(thirdUpload).not.to.be(null); // Get the asset ID and versions of the uploaded asset - const resourceResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, {versions: true}); + const resourceResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, { versions: true }); const assetId = resourceResp.asset_id; const firstAssetVersion = resourceResp.versions[0].version_id; const secondAssetVersion = resourceResp.versions[1].version_id; @@ -986,12 +983,12 @@ describe("api", function () { // Delete the first version const removeSingleVersion = await cloudinary.v2.api.delete_backed_up_assets(assetId, firstAssetVersion); - const removeSingleVersionResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, {versions: true}); + const removeSingleVersionResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, { versions: true }); expect(removeSingleVersionResp.versions).not.to.contain(firstAssetVersion); // Delete the remaining two versions const removeMultipleVersions = await cloudinary.v2.api.delete_backed_up_assets(assetId, [secondAssetVersion, thirdAssetVersion]); - const removeMultipleVersionsResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, {versions: true}); + const removeMultipleVersionsResp = await API_V2.resource(PUBLIC_ID_BACKUP_1, { versions: true }); expect(removeMultipleVersionsResp.versions).not.to.contain(secondAssetVersion); expect(removeMultipleVersionsResp.versions).not.to.contain(thirdAssetVersion); }); @@ -1050,8 +1047,8 @@ describe("api", function () { const mocked = helper.mockTest(); const qualityValues = ["auto:advanced", "auto:best", "80:420", "none"]; qualityValues.forEach(quality => { - it("should support '" + quality + "' in update", function () { - cloudinary.v2.api.update("sample", {quality_override: quality}); + it("should support '" + quality + "' in update", async function () { + await cloudinary.v2.api.update("sample", { quality_override: quality }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.write, sinon.match(helper.apiParamMatcher("quality_override", quality))); }); }); @@ -1072,7 +1069,7 @@ describe("api", function () { } // Update an image with ocr parameter const ocrType = "adv_ocr"; - const updateResult = await API_V2.update(PUBLIC_ID_OCR_1, {ocr: ocrType}); + const updateResult = await API_V2.update(PUBLIC_ID_OCR_1, { ocr: ocrType }); // Ensure result includes a ocr with correct value expect(updateResult).not.to.be.empty(); @@ -1085,10 +1082,10 @@ describe("api", function () { this.skip(); } this.timeout(TIMEOUT.MEDIUM); - return API_V2.update(PUBLIC_ID_OCR_1, {ocr: 'illegal'}) + return API_V2.update(PUBLIC_ID_OCR_1, { ocr: 'illegal' }) .then( () => expect().fail() - ).catch(({error}) => expect(error.message).to.contain("Illegal value")); + ).catch(({ error }) => expect(error.message).to.contain("Illegal value")); }); }); it("should support setting manual moderation status", function () { @@ -1107,7 +1104,7 @@ describe("api", function () { raw_convert: "illegal" })).then( () => expect().fail() - ).catch(({error}) => expect(error.message).to.contain("Illegal value")); + ).catch(({ error }) => expect(error.message).to.contain("Illegal value")); }); it("should support requesting categorization", function () { this.timeout(TIMEOUT.MEDIUM); @@ -1117,7 +1114,7 @@ describe("api", function () { }); }).then(() => { expect().fail(); - }).catch(function ({error}) { + }).catch(function ({ error }) { expect(error.message).to.contain("Illegal value"); }); }); @@ -1128,7 +1125,7 @@ describe("api", function () { detection: "illegal" })).then( () => expect().fail() - ).catch(({error}) => expect(error.message).to.contain("Illegal value")); + ).catch(({ error }) => expect(error.message).to.contain("Illegal value")); }); it("should support requesting background_removal", function () { this.timeout(TIMEOUT.MEDIUM); @@ -1137,7 +1134,7 @@ describe("api", function () { background_removal: "illegal" })).then( () => expect().fail() - ).catch(({error}) => expect(error.message).to.contain("Illegal value")); + ).catch(({ error }) => expect(error.message).to.contain("Illegal value")); }); describe("access_control", function () { var acl, acl_string, options; @@ -1152,40 +1149,43 @@ describe("api", function () { tags: [...UPLOAD_TAGS, 'access_control_test'] }; it("should allow the user to define ACL in the update parameters2", function () { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { options.access_control = [acl]; - cloudinary.v2.api.update("id", options); - return sinon.assert.calledWith( + await cloudinary.v2.api.update("id", options).catch(helper.ignoreApiFailure); + sinon.assert.calledWith( writeSpy, sinon.match(arg => helper.apiParamMatcher('access_control', `[${acl_string}]`)(arg)) ); }); }); }); }); - it("should support listing by moderation kind and value", function () { - callReusableTest("a list with a cursor", cloudinary.v2.api.resources_by_moderation, "manual", "approved"); - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => ["approved", "pending", "rejected"].forEach((stat) => { - var status, status2; - status = stat; - status2 = status; - requestSpy.resetHistory(); - cloudinary.v2.api.resources_by_moderation("manual", status2, { - moderations: true - }); - sinon.assert.calledWith(requestSpy, sinon.match( - arg => new RegExp(`/resources/image/moderations/manual/${status2}$`).test(arg != null ? arg.pathname : void 0), `/resources/image/moderations/manual/${status}` - )); - sinon.assert.calledWith(requestSpy, sinon.match( - arg => (arg != null ? arg.query : void 0) === "moderations=true", "moderations=true" - )); - })); + callReusableTest("a list with a cursor", cloudinary.v2.api.resources_by_moderation, "manual", "approved"); + it("should support listing by moderation kind and value", async function () { + for (const stat of ["approved", "pending", "rejected"]) { + // eslint-disable-next-line no-await-in-loop + await helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + var status, status2; + status = stat; + status2 = status; + requestSpy.resetHistory(); + await cloudinary.v2.api.resources_by_moderation("manual", status2, { + moderations: true + }); + sinon.assert.calledWith(requestSpy, sinon.match( + arg => new RegExp(`/resources/image/moderations/manual/${status2}$`).test(arg != null ? arg.pathname : void 0), `/resources/image/moderations/manual/${status}` + )); + sinon.assert.calledWith(requestSpy, sinon.match( + arg => (arg != null ? arg.query : void 0) === "moderations=true", "moderations=true" + )); + }); + } }); describe("folders", function () { // For this test to work, "Auto-create folders" should be enabled in the Upload Settings. // Replace `it` with `it.skip` below if you want to disable it. it("should list folders in cloudinary", function () { this.timeout(TIMEOUT.LONG); - return Q.all([ + return Promise.all([ uploadImage({ public_id: 'test_folder1/item', tags: UPLOAD_TAGS @@ -1207,8 +1207,8 @@ describe("api", function () { tags: UPLOAD_TAGS }) ]).then(wait(TIMEOUT.SHORT)) - .then(function (results) { - return Q.all([cloudinary.v2.api.root_folders(), cloudinary.v2.api.sub_folders('test_folder1')]); + .then(function () { + return Promise.all([cloudinary.v2.api.root_folders(), cloudinary.v2.api.sub_folders('test_folder1')]); }).then(function (results) { var folder, root, root_folders, sub_1; root = results[0]; @@ -1228,17 +1228,17 @@ describe("api", function () { expect(sub_1.folders[0].path).to.eql('test_folder1/test_subfolder1'); expect(sub_1.folders[1].path).to.eql('test_folder1/test_subfolder2'); return cloudinary.v2.api.sub_folders('test_folder_not_exists'); - }).then(wait(TIMEOUT.LONG)).then((result) => { + }).then(wait(TIMEOUT.LONG)).then(() => { console.log('error test_folder_not_exists should not pass to "then" handler but "catch"'); expect().fail('error test_folder_not_exists should not pass to "then" handler but "catch"'); - }).catch(({error}) => expect(error.message).to.eql('Can\'t find folder with path test_folder_not_exists')); + }).catch(({ error }) => expect(error.message).to.eql('Can\'t find folder with path test_folder_not_exists')); }); describe("create_folder", function () { it("should create a new folder", function () { const folderPath = `${UNIQUE_TEST_FOLDER}`; const expectedPath = `folders/${folderPath}`; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.api.create_folder(folderPath); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.api.create_folder(folderPath); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(expectedPath), method: sinon.match("POST") @@ -1253,7 +1253,7 @@ describe("api", function () { return uploadImage({ folder: folderPath, tags: UPLOAD_TAGS - }).delay(2 * 1000).then(function () { + }).then(wait(2 * 1000)).then(function () { return cloudinary.v2.api.delete_resources_by_prefix(folderPath) .then(() => cloudinary.v2.api.sub_folders(folderPath).then(folder => { expect(folder).not.to.be(null); @@ -1266,9 +1266,9 @@ describe("api", function () { this.timeout(TIMEOUT.MEDIUM); return cloudinary.v2.api.delete_folder( folderPath - ).delay(2 * 1000).then(() => cloudinary.v2.api.sub_folders(folderPath) + ).then(wait(2 * 1000)).then(() => cloudinary.v2.api.sub_folders(folderPath) ).then(() => expect().fail() - ).catch(({error}) => expect(error.message).to.contain("Can't find folder with path")); + ).catch(({ error }) => expect(error.message).to.contain("Can't find folder with path")); }); }); describe("root_folders", function () { @@ -1289,7 +1289,7 @@ describe("api", function () { use_asset_folder_as_public_id_prefix: true }) let preset_details = await cloudinary.v2.api.upload_preset(preset.name); - expect(preset_details.settings).to.eql({use_asset_folder_as_public_id_prefix: true}) + expect(preset_details.settings).to.eql({ use_asset_folder_as_public_id_prefix: true }) }); it('should update upload_preset when use_asset_folder_as_public_id_prefix is true', async function () { @@ -1304,7 +1304,7 @@ describe("api", function () { }); let preset_details = await cloudinary.v2.api.upload_preset(preset.name); - expect(preset_details.settings).to.eql({use_asset_folder_as_public_id_prefix: true}) + expect(preset_details.settings).to.eql({ use_asset_folder_as_public_id_prefix: true }) }); it('should update asset_folder', async function () { @@ -1323,16 +1323,13 @@ describe("api", function () { }); }); - it('should update asset_folder with unique_display_name', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - uploadImage().then(result => { - cloudinary.v2.api.update(result.public_id, { - unique_display_name: true - }) - return sinon.assert.calledWith(requestSpy, sinon.match({ - query: sinon.match(helper.apiParamMatcher("unique_display_name", "true")) - })); - }); + it('should update asset_folder with unique_display_name', async () => { + const result = await uploadImage() + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.update(result.public_id, { + unique_display_name: true + }) + sinon.assert.calledWith(writeSpy, sinon.match(helper.apiParamMatcher('unique_display_name', true, "unique_display_name=true"))); }); }); @@ -1392,7 +1389,7 @@ describe("api", function () { this.deleteMapping = false; return cloudinary.v2.api.upload_mappings(); }).then( - ({mappings}) => expect(mappings.find(({folder}) => folder === this.mapping)).not.to.be.ok() + ({ mappings }) => expect(mappings.find(({ folder }) => folder === this.mapping)).not.to.be.ok() ); }); }); @@ -1508,24 +1505,24 @@ describe("api", function () { }); describe("proxy support", function () { const mocked = helper.mockTest(); - it("should support proxy for api calls", function () { - cloudinary.config({api_proxy: "https://myuser:mypass@example.com"}); - cloudinary.v2.api.resources({}); + it("should support proxy for api calls", async function () { + cloudinary.config({ api_proxy: "https://myuser:mypass@example.com" }); + await cloudinary.v2.api.resources({}).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match( arg => arg.agent instanceof https.Agent )); }); - it("should prioritize custom agent", function () { - cloudinary.config({api_proxy: "https://myuser:mypass@example.com"}); + it("should prioritize custom agent", async function () { + cloudinary.config({ api_proxy: "https://myuser:mypass@example.com" }); const custom_agent = https.Agent() - cloudinary.v2.api.resources({agent: custom_agent}); + await cloudinary.v2.api.resources({ agent: custom_agent }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match( arg => arg.agent === custom_agent )); }); - it("should support api_proxy as options key", function () { + it("should support api_proxy as options key", async function () { cloudinary.config({}); - cloudinary.v2.api.resources({api_proxy: "https://myuser:mypass@example.com"}); + await cloudinary.v2.api.resources({ api_proxy: "https://myuser:mypass@example.com" }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match( arg => arg.agent instanceof https.Agent )); @@ -1534,7 +1531,7 @@ describe("api", function () { describe('config hide_sensitive', () => { it("should hide API key and secret upon error when `hide_sensitive` is true", async function () { try { - cloudinary.config({hide_sensitive: true}); + cloudinary.config({ hide_sensitive: true }); const result = await cloudinary.v2.api.resource("?"); expect(result).fail(); } catch (err) { @@ -1544,7 +1541,7 @@ describe("api", function () { it("should hide Authorization header upon error when `hide_sensitive` is true", async function () { try { - cloudinary.config({hide_sensitive: true}); + cloudinary.config({ hide_sensitive: true }); const result = await cloudinary.v2.api.resource("?", { oauth_token: 'irrelevant' }); expect(result).fail(); } catch (err) { diff --git a/test/integration/api/admin/config_spec.js b/test/integration/api/admin/config_spec.js index b0c2ecc8..0b42ac0a 100644 --- a/test/integration/api/admin/config_spec.js +++ b/test/integration/api/admin/config_spec.js @@ -2,6 +2,7 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const api_http = require("https"); +const helper = require('../../../spechelper'); const ClientRequest = require('_http_client').ClientRequest; describe('Admin API - Config', () => { @@ -20,8 +21,8 @@ describe('Admin API - Config', () => { }); describe('config', () => { - it('should send a request to config endpoint', () => { - cloudinary.v2.api.config(); + it('should send a request to config endpoint', async () => { + await cloudinary.v2.api.config().catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match('config'), @@ -30,8 +31,8 @@ describe('Admin API - Config', () => { })); }); - it('should send a request to config endpoint with optional parameters', () => { - cloudinary.v2.api.config({ settings: true }); + it('should send a request to config endpoint with optional parameters', async () => { + await cloudinary.v2.api.config({ settings: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match('config'), diff --git a/test/integration/api/admin/folders_api_spec.js b/test/integration/api/admin/folders_api_spec.js index c514ddde..ceaf7d0f 100644 --- a/test/integration/api/admin/folders_api_spec.js +++ b/test/integration/api/admin/folders_api_spec.js @@ -23,8 +23,8 @@ describe('Admin API - Folders', () => { }); describe('rename_folder', () => { - it('should send a request to update folder endpoint with correct parameters', () => { - cloudinary.v2.api.rename_folder('old/path', 'new/path'); + it('should send a request to update folder endpoint with correct parameters', async () => { + await cloudinary.v2.api.rename_folder('old/path', 'new/path').catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match('old%2Fpath'), diff --git a/test/integration/api/admin/related_assets_spec.js b/test/integration/api/admin/related_assets_spec.js index 9f1ba589..66f95ba6 100644 --- a/test/integration/api/admin/related_assets_spec.js +++ b/test/integration/api/admin/related_assets_spec.js @@ -15,8 +15,8 @@ describe('Asset relations API', () => { describe('when creating new relation', () => { describe('using public id', () => { it('should allow passing a single public id to create a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.add_related_assets(testPublicId, singleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.add_related_assets(testPublicId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -27,8 +27,8 @@ describe('Asset relations API', () => { }); it('should allow passing multiple public ids to create a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.add_related_assets(testPublicId, multipleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.add_related_assets(testPublicId, multipleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -41,8 +41,8 @@ describe('Asset relations API', () => { describe('using asset id', () => { it('should allow passing a single public id to create a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, singleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -53,8 +53,8 @@ describe('Asset relations API', () => { }); it('should allow passing multiple public ids to create a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, multipleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, multipleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -69,8 +69,8 @@ describe('Asset relations API', () => { describe('when deleting existing relation', () => { describe('using public id', () => { it('should allow passing a single public id to delete a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.delete_related_assets(testPublicId, singleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.delete_related_assets(testPublicId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -81,8 +81,8 @@ describe('Asset relations API', () => { }); it('should allow passing multiple public ids to delete a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.delete_related_assets(testPublicId, multipleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.delete_related_assets(testPublicId, multipleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -95,8 +95,8 @@ describe('Asset relations API', () => { describe('and using asset id', () => { it('should allow passing a single public id to delete a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.delete_related_assets_by_asset_id(testAssetId, singleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.delete_related_assets_by_asset_id(testAssetId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -107,8 +107,8 @@ describe('Asset relations API', () => { }); it('should allow passing multiple public ids to delete a relation', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.delete_related_assets_by_asset_id(testAssetId, multipleRelatedPublicId); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.delete_related_assets_by_asset_id(testAssetId, multipleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); diff --git a/test/integration/api/admin/structured_metadata_spec.js b/test/integration/api/admin/structured_metadata_spec.js index 8885f502..d28e43c1 100644 --- a/test/integration/api/admin/structured_metadata_spec.js +++ b/test/integration/api/admin/structured_metadata_spec.js @@ -1,9 +1,9 @@ const assert = require('assert'); -const Q = require('q'); const sinon = require('sinon'); const cloudinary = require("../../../../cloudinary"); const helper = require("../../../spechelper"); const TIMEOUT = require('../../../testUtils/testConstants').TIMEOUT; +const allSettled = require('../../../testUtils/helpers/allSettled'); const TEST_ID = Date.now(); @@ -87,33 +87,30 @@ function createMetadataFieldForTest(field) { if (!field.label) { field.label = field.external_id; } - return api.add_metadata_field(field); + return api.add_metadata_field(field).catch(helper.ignoreApiFailure); } describe("structured metadata api", function () { this.timeout(TIMEOUT.LARGE); - before(function () { - // Create the metadata fields required for the tests - return Q.allSettled( - metadata_fields_to_create.map(field => createMetadataFieldForTest(field)) - ).finally(function () { + before(async function () { + await Promise.all(metadata_fields_to_create.map(field => createMetadataFieldForTest(field))).catch(error => { + console.error('Error creating metadata fields:', error); }); }); after(function () { - // Delete all metadata fields created during testing - return Q.allSettled( + // allSettled finishes successfully if all promises finish, even if some reject + return allSettled( metadata_fields_external_ids.map(field => api.delete_metadata_field(field)) - ).finally(function () { - }); + ); }); describe("list_metadata_fields", function () { it("should return all metadata field definitions", function () { const expectedPath = `/metadata_fields$`; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - api.list_metadata_fields(); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await api.list_metadata_fields(); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("GET") @@ -126,7 +123,7 @@ describe("structured metadata api", function () { it("should return metadata field by external id", function () { return api.metadata_field_by_field_id(EXTERNAL_ID_GENERAL) .then((result) => { - expect([result, {label: EXTERNAL_ID_GENERAL}]).to.beAMetadataField(); + expect([result, { label: EXTERNAL_ID_GENERAL }]).to.beAMetadataField(); }); }); }); @@ -134,13 +131,13 @@ describe("structured metadata api", function () { describe("add_metadata_field", function () { const expectedPath = "/metadata_fields$"; it("should create string metadata field", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { const metadata = { external_id: EXTERNAL_ID_STRING, label: EXTERNAL_ID_STRING, type: 'string' }; - api.add_metadata_field(metadata); + await api.add_metadata_field(metadata); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("POST") @@ -151,13 +148,13 @@ describe("structured metadata api", function () { }); }); it("should create integer metadata field", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { const metadata = { external_id: EXTERNAL_ID_INT, label: EXTERNAL_ID_INT, type: 'integer' }; - api.add_metadata_field(metadata); + await api.add_metadata_field(metadata); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("POST") @@ -184,7 +181,7 @@ describe("structured metadata api", function () { }); }); it("should create enum metadata field", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { const metadata = { datasource: { values: datasource_single @@ -193,7 +190,7 @@ describe("structured metadata api", function () { label: EXTERNAL_ID_ENUM, type: 'enum' }; - api.add_metadata_field(metadata); + await api.add_metadata_field(metadata); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("POST") @@ -201,7 +198,7 @@ describe("structured metadata api", function () { sinon.assert.calledWith(writeSpy, sinon.match(helper.apiJsonParamMatcher('external_id', EXTERNAL_ID_ENUM))); sinon.assert.calledWith(writeSpy, sinon.match(helper.apiJsonParamMatcher('type', 'enum'))); sinon.assert.calledWith(writeSpy, sinon.match(helper.apiJsonParamMatcher('label', EXTERNAL_ID_ENUM))); - sinon.assert.calledWith(writeSpy, sinon.match(helper.apiJsonParamMatcher('datasource', {values: datasource_single}))); + sinon.assert.calledWith(writeSpy, sinon.match(helper.apiJsonParamMatcher('datasource', { values: datasource_single }))); }); }); it("should create set metadata field", function () { @@ -269,7 +266,7 @@ describe("structured metadata api", function () { describe("update_metadata_field_datasource", function () { it("should update metadata field datasource by external id", function () { - return api.update_metadata_field_datasource(EXTERNAL_ID_ENUM_2, {values: datasource_single}) + return api.update_metadata_field_datasource(EXTERNAL_ID_ENUM_2, { values: datasource_single }) .then(() => api.metadata_field_by_field_id(EXTERNAL_ID_ENUM_2)) .then((result) => { expect(result.datasource).to.beADatasource(); @@ -280,9 +277,9 @@ describe("structured metadata api", function () { describe("delete_metadata_field", function () { it("should delete metadata field by external id", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { const expectedPath = `/metadata_fields/${EXTERNAL_ID_DELETE}$`; - api.delete_metadata_field(EXTERNAL_ID_DELETE); + await api.delete_metadata_field(EXTERNAL_ID_DELETE).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("DELETE") @@ -301,7 +298,7 @@ describe("structured metadata api", function () { expect(result.message).to.eql("ok"); return api.add_metadata_field(metadata); }) - .catch(({error}) => { + .catch(({ error }) => { expect(error).not.to.be(void 0); expect(error.http_code).to.eql(400); expect(error.message).to.contain(`external id ${EXTERNAL_ID_DELETE_2} already exists`); @@ -411,7 +408,7 @@ describe("structured metadata api", function () { .then((result) => { expect(result).to.beAMetadataField(); return api.metadata_field_by_field_id(EXTERNAL_ID_INT_VALIDATION_2); - }).catch(({error}) => { + }).catch(({ error }) => { expect(error).not.to.be(void 0); expect(error.http_code).to.eql(400); expect(error.message).to.contain(`default_value is invalid`); @@ -446,8 +443,8 @@ describe("structured metadata api", function () { const method = /^PUT$/ it("should reorder the metadata fields for label order by asc", function () { - helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - api.reorder_metadata_fields("label", "asc"); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await api.reorder_metadata_fields("label", "asc"); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(pathname), @@ -460,8 +457,8 @@ describe("structured metadata api", function () { }); it("should reorder the metadata fields for external_id order by desc", function () { - helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - api.reorder_metadata_fields("external_id", "desc"); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await api.reorder_metadata_fields("external_id", "desc"); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(pathname), @@ -474,8 +471,8 @@ describe("structured metadata api", function () { }); it("should reorder the metadata fields for for created_at order by asc", function () { - helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - api.reorder_metadata_fields("created_at", "asc"); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await api.reorder_metadata_fields("created_at", "asc"); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(pathname), @@ -520,7 +517,7 @@ describe("structured metadata api", function () { }; api.add_metadata_field(metadata, (res, res2) => { - cloudinary.v2.uploader.update_metadata({[EXTERNAL_ID_SET_4]: [1]}, ['sample'], (err, result) => { + cloudinary.v2.uploader.update_metadata({ [EXTERNAL_ID_SET_4]: [1] }, ['sample'], (err, result) => { expect(typeof err).to.be('undefined'); expect(result.public_ids[0]).to.equal('sample'); done(); @@ -531,8 +528,8 @@ describe("structured metadata api", function () { describe('rules', () => { it('should allow listing metadata rules', () => { const expectedPath = '/metadata_rules'; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - api.list_metadata_rules(); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await api.list_metadata_rules().catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match('GET') @@ -542,14 +539,14 @@ describe("structured metadata api", function () { it('should allow adding new metadata rules', () => { const expectedPath = '/metadata_rules'; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { const newMetadataRule = { metadata_field_id: 'field_id', name: 'rule_name', condition: {}, result: {} }; - api.add_metadata_rule(newMetadataRule); + await api.add_metadata_rule(newMetadataRule).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -570,7 +567,7 @@ describe("structured metadata api", function () { it('should allow editing metadata rules', () => { const expectedPath = '/metadata_rules/some-metadata-rule-id'; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { const ruleUpdate = { metadata_field_id: 'new_field_id', name: 'new_rule_name', @@ -578,7 +575,7 @@ describe("structured metadata api", function () { result: {}, state: 'inactive' }; - api.update_metadata_rule('some-metadata-rule-id', ruleUpdate); + await api.update_metadata_rule('some-metadata-rule-id', ruleUpdate).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -600,8 +597,8 @@ describe("structured metadata api", function () { it('should allow removing existing metadata rules', () => { const expectedPath = '/metadata_rules/some-metadata-rule-id'; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - api.delete_metadata_rule('some-metadata-rule-id'); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await api.delete_metadata_rule('some-metadata-rule-id').catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match('DELETE') diff --git a/test/integration/api/analysis/analyze_spec.js b/test/integration/api/analysis/analyze_spec.js index 644b382d..8ebd0899 100644 --- a/test/integration/api/analysis/analyze_spec.js +++ b/test/integration/api/analysis/analyze_spec.js @@ -25,8 +25,8 @@ describe('Analyze API', () => { mocked.xhr.restore(); }); - it('should call analyze endpoint with non-custom analysis_type', () => { - cloudinary.analysis.analyze_uri('https://example.com', 'captioning'); + it('should call analyze endpoint with non-custom analysis_type', async () => { + await cloudinary.analysis.analyze_uri('https://example.com', 'captioning').catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match(new RegExp(`/v2/${config.cloud_name}/analysis/analyze/uri`)), @@ -36,11 +36,11 @@ describe('Analyze API', () => { sinon.assert.calledWith(mocked.write, sinon.match(helper.apiJsonParamMatcher('analysis_type', 'captioning'))); }); - it('should call analyze endpoint with custom analysis_type', () => { - cloudinary.analysis.analyze_uri('https://example.com', 'custom', { + it('should call analyze endpoint with custom analysis_type', async () => { + await cloudinary.analysis.analyze_uri('https://example.com', 'custom', { model_name: 'my_model', model_version: 1 - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match(new RegExp(`/v2/${config.cloud_name}/analysis/analyze/uri`)), diff --git a/test/integration/api/authorization/oAuth_authorization_spec.js b/test/integration/api/authorization/oAuth_authorization_spec.js index 6cc2283a..f8dd1016 100644 --- a/test/integration/api/authorization/oAuth_authorization_spec.js +++ b/test/integration/api/authorization/oAuth_authorization_spec.js @@ -8,9 +8,9 @@ const { PUBLIC_ID } = PUBLIC_IDS; describe("oauth_token", function(){ it("should send the oauth_token option to the server (admin_api)", function() { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.resource(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }); - return sinon.assert.calledWith(requestSpy, + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.resource(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -18,14 +18,14 @@ describe("oauth_token", function(){ }); it("should send the oauth_token config to the server (admin_api)", function() { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { cloudinary.config({ api_key: undefined, api_secret: undefined, oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }); - cloudinary.v2.api.resource(PUBLIC_ID); - return sinon.assert.calledWith(requestSpy, + await cloudinary.v2.api.resource(PUBLIC_ID).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -38,9 +38,9 @@ describe("oauth_token", function(){ api_secret: "1234", oauth_token: undefined }); - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.resource(PUBLIC_ID); - return sinon.assert.calledWith(requestSpy, sinon.match({ auth: "1234:1234" })); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.resource(PUBLIC_ID).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(requestSpy, sinon.match({ auth: "1234:1234" })); }); }); @@ -67,9 +67,9 @@ describe("oauth_token", function(){ }); it("should send the oauth_token option to the server (upload_api)", function() { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.uploader.upload(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }); - return sinon.assert.calledWith(requestSpy, + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.uploader.upload(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -77,14 +77,14 @@ describe("oauth_token", function(){ }); it("should send the oauth_token config to the server (upload_api)", function() { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { cloudinary.config({ api_key: undefined, api_secret: undefined, oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }); - cloudinary.v2.uploader.upload(PUBLIC_ID); - return sinon.assert.calledWith(requestSpy, + await cloudinary.v2.uploader.upload(PUBLIC_ID).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -97,9 +97,9 @@ describe("oauth_token", function(){ api_secret: "1234", oauth_token: undefined }); - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.uploader.upload(PUBLIC_ID) - return sinon.assert.calledWith(requestSpy, sinon.match({ auth: null })); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.uploader.upload(PUBLIC_ID).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(requestSpy, sinon.match({ auth: null })); }); }); @@ -120,9 +120,9 @@ describe("oauth_token", function(){ api_secret: undefined, oauth_token: undefined }); - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.uploader.unsigned_upload(PUBLIC_ID, 'preset') - return sinon.assert.calledWith(requestSpy, sinon.match({ auth: null })); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.uploader.unsigned_upload(PUBLIC_ID, 'preset').catch(helper.ignoreApiFailure); + sinon.assert.calledWith(requestSpy, sinon.match({ auth: null })); }); }); }); diff --git a/test/integration/api/search/search_spec.js b/test/integration/api/search/search_spec.js index 58528854..0167a01e 100644 --- a/test/integration/api/search/search_spec.js +++ b/test/integration/api/search/search_spec.js @@ -1,11 +1,10 @@ -const Q = require('q'); const cloudinary = require('../../../../cloudinary'); const helper = require("../../../spechelper"); const testConstants = require('../../../testUtils/testConstants'); const describe = require('../../../testUtils/suite'); -const exp = require("constants"); -const cluster = require("cluster"); const assert = require("assert"); +const allSettled = require('../../../testUtils/helpers/allSettled'); +const wait = require('../../../testUtils/helpers/wait'); const { TIMEOUT, TAGS, @@ -30,7 +29,7 @@ describe("search_api", function () { describe("integration", function () { this.timeout(TIMEOUT.LONG); before(function () { - return Q.allSettled([ + return allSettled([ cloudinary.v2.uploader.upload(helper.IMAGE_FILE, { public_id: PUBLIC_ID_1, @@ -52,7 +51,8 @@ describe("search_api", function () { SEARCH_TAG], context: "stage=validated" }) - ]).delay(10000) + ]) + .then(wait(10000)) .then((uploadResults) => { uploadResults.forEach(({value}) => { ASSET_IDS.push(value.asset_id); @@ -60,11 +60,11 @@ describe("search_api", function () { }); }); - after(function () { + after(async function () { if (!cloudinary.config().keep_test_products) { let config = cloudinary.config(); - cloudinary.v2.api.delete_resources_by_tag(SEARCH_TAG); + await cloudinary.v2.api.delete_resources_by_tag(SEARCH_TAG); } }); it(`should return all images tagged with ${SEARCH_TAG}`, function () { diff --git a/test/integration/api/search/visual_search_spec.js b/test/integration/api/search/visual_search_spec.js index 6bf388a4..a7f2a7ae 100644 --- a/test/integration/api/search/visual_search_spec.js +++ b/test/integration/api/search/visual_search_spec.js @@ -8,8 +8,8 @@ const {TEST_CLOUD_NAME} = require('../../../testUtils/testConstants'); describe('Visual search', () => { it('should pass the image_url parameter to the api call', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.visual_search({image_url: 'test-image-url'}); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.visual_search({image_url: 'test-image-url'}).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'GET'); @@ -18,8 +18,8 @@ describe('Visual search', () => { }); it('should pass the image_url parameter to the api call', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.visual_search({image_asset_id: 'image-asset-id'}); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.visual_search({image_asset_id: 'image-asset-id'}).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'GET'); @@ -28,8 +28,8 @@ describe('Visual search', () => { }); it('should pass the image_url parameter to the api call', () => { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.api.visual_search({text: 'visual-search-input'}); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.api.visual_search({text: 'visual-search-input'}).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'GET'); diff --git a/test/integration/api/uploader/archivespec.js b/test/integration/api/uploader/archivespec.js index 4dec4d3b..5e9681f4 100644 --- a/test/integration/api/uploader/archivespec.js +++ b/test/integration/api/uploader/archivespec.js @@ -2,7 +2,6 @@ const https = require('https'); const last = require('lodash/last'); const sinon = require("sinon"); const execSync = require('child_process').execSync; -const Q = require('q'); const fs = require('fs'); const os = require('os'); const describe = require('../../../testUtils/suite'); @@ -43,7 +42,7 @@ describe("archive", function () { this.timeout(TIMEOUT.LONG); before(function () { - return Q.all([ + return Promise.all([ uploader.upload(IMAGE_URL, { public_id: PUBLIC_ID1, @@ -150,8 +149,8 @@ describe("archive", function () { describe('.create_zip', function () { this.timeout(TIMEOUT.LONG); it('should call create_archive with "zip" format and ignore missing resources', function () { - helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - uploader.create_zip({ + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await uploader.create_zip({ tags: TEST_TAG, public_ids: [PUBLIC_ID_RAW, "non-existing-resource"], resource_type: "raw", diff --git a/test/integration/api/uploader/auto_chaptering_spec.js b/test/integration/api/uploader/auto_chaptering_spec.js index 54ad7e02..0843cbd0 100644 --- a/test/integration/api/uploader/auto_chaptering_spec.js +++ b/test/integration/api/uploader/auto_chaptering_spec.js @@ -21,15 +21,15 @@ describe('Uploader', () => { }); describe('upload', () => { - it('should send a request with auto_chaptering set to true if requested', () => { - cloudinary.v2.uploader.upload('irrelevant', { auto_chaptering: true }); + it('should send a request with auto_chaptering set to true if requested', async () => { + await cloudinary.v2.uploader.upload('irrelevant', { auto_chaptering: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('auto_chaptering', '1'))); }); }); describe('explicit', () => { - it('should send a request with auto_chaptering set to true if requested', () => { - cloudinary.v2.uploader.explicit('irrelevant', { auto_chaptering: true }); + it('should send a request with auto_chaptering set to true if requested', async () => { + await cloudinary.v2.uploader.explicit('irrelevant', { auto_chaptering: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('auto_chaptering', '1'))); }); }); diff --git a/test/integration/api/uploader/auto_transcription_spec.js b/test/integration/api/uploader/auto_transcription_spec.js index 2f7522c9..93eff4ce 100644 --- a/test/integration/api/uploader/auto_transcription_spec.js +++ b/test/integration/api/uploader/auto_transcription_spec.js @@ -18,25 +18,25 @@ describe('Uploader', () => { }); describe('upload', () => { - it('should send a request with auto_transcription set to true if requested', () => { - cloudinary.v2.uploader.upload('irrelevant', { auto_transcription: true }); + it('should send a request with auto_transcription set to true if requested', async () => { + await cloudinary.v2.uploader.upload('irrelevant', { auto_transcription: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('auto_transcription', '1'))); }); - it('should send a request with auto_transcription config if requested', () => { - cloudinary.v2.uploader.upload('irrelevant', { auto_transcription: { translate: ['pl'] } }); + it('should send a request with auto_transcription config if requested', async () => { + await cloudinary.v2.uploader.upload('irrelevant', { auto_transcription: { translate: ['pl'] } }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('auto_transcription', '{"translate":["pl"]}'))); }); }); describe('explicit', () => { - it('should send a request with auto_transcription set to true if requested', () => { - cloudinary.v2.uploader.explicit('irrelevant', { auto_transcription: true }); + it('should send a request with auto_transcription set to true if requested', async () => { + await cloudinary.v2.uploader.explicit('irrelevant', { auto_transcription: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('auto_transcription', '1'))); }); - it('should send a request with auto_transcription config if requested', () => { - cloudinary.v2.uploader.explicit('irrelevant', { auto_transcription: { translate: ['pl'] } }); + it('should send a request with auto_transcription config if requested', async () => { + await cloudinary.v2.uploader.explicit('irrelevant', { auto_transcription: { translate: ['pl'] } }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('auto_transcription', '{"translate":["pl"]}'))); }); }); diff --git a/test/integration/api/uploader/custom_region_spec.js b/test/integration/api/uploader/custom_region_spec.js index 7e7e61c5..074b3aee 100644 --- a/test/integration/api/uploader/custom_region_spec.js +++ b/test/integration/api/uploader/custom_region_spec.js @@ -21,13 +21,13 @@ describe('Uploader', () => { }); describe('upload', () => { - it('should send a request with encoded custom region gravity that represents a box', () => { - cloudinary.v2.uploader.upload('irrelevant', { + it('should send a request with encoded custom region gravity that represents a box', async () => { + await cloudinary.v2.uploader.upload('irrelevant', { regions: { 'box_1': [[1, 2], [3, 4]], 'box_2': [[5, 6], [7, 8]] } - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'box_1': [[1, 2], [3, 4]], @@ -35,13 +35,13 @@ describe('Uploader', () => { })))); }); - it('should send a request with encoded custom region gravity that represents a custom shape', () => { - cloudinary.v2.uploader.upload('irrelevant', { + it('should send a request with encoded custom region gravity that represents a custom shape', async () => { + await cloudinary.v2.uploader.upload('irrelevant', { regions: { 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], 'custom_2': [[10, 11], [12, 13], [14, 15]] } - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], @@ -51,13 +51,13 @@ describe('Uploader', () => { }); describe('explicit', () => { - it('should send a request with encoded custom region gravity that represents a box', () => { - cloudinary.v2.uploader.explicit('irrelevant', { + it('should send a request with encoded custom region gravity that represents a box', async () => { + await cloudinary.v2.uploader.explicit('irrelevant', { regions: { 'box_1': [[1, 2], [3, 4]], 'box_2': [[5, 6], [7, 8]] } - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'box_1': [[1, 2], [3, 4]], @@ -65,13 +65,13 @@ describe('Uploader', () => { })))); }); - it('should send a request with encoded custom region gravity that represents a custom shape', () => { - cloudinary.v2.uploader.explicit('irrelevant', { + it('should send a request with encoded custom region gravity that represents a custom shape', async () => { + await cloudinary.v2.uploader.explicit('irrelevant', { regions: { 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], 'custom_2': [[10, 11], [12, 13], [14, 15]] } - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], diff --git a/test/integration/api/uploader/slideshow_spec.js b/test/integration/api/uploader/slideshow_spec.js index 1a82cf3b..8278cbdf 100644 --- a/test/integration/api/uploader/slideshow_spec.js +++ b/test/integration/api/uploader/slideshow_spec.js @@ -1,4 +1,3 @@ -const Q = require('q'); const cloudinary = require("../../../../cloudinary"); const describe = require('../../../testUtils/suite'); const TEST_ID = Date.now(); @@ -6,6 +5,7 @@ const TEST_ID = Date.now(); const createTestConfig = require('../../../testUtils/createTestConfig'); const testConstants = require('../../../testUtils/testConstants'); +const allSettled = require("../../../testUtils/helpers/allSettled"); const UPLOADER_V2 = cloudinary.v2.uploader; const { @@ -26,7 +26,7 @@ describe("create slideshow tests", function () { if (!(config.api_key && config.api_secret)) { expect().fail("Missing key and secret. Please set CLOUDINARY_URL."); } - return Q.allSettled([ + return allSettled([ !cloudinary.config().keep_test_products ? cloudinary.v2.api.delete_resources_by_tag(TEST_TAG) : void 0, !cloudinary.config().keep_test_products ? cloudinary.v2.api.delete_resources_by_tag(TEST_TAG, { diff --git a/test/integration/api/uploader/uploader_spec.js b/test/integration/api/uploader/uploader_spec.js index ae105ce7..7198b6b7 100644 --- a/test/integration/api/uploader/uploader_spec.js +++ b/test/integration/api/uploader/uploader_spec.js @@ -2,7 +2,6 @@ const https = require('https'); const http = require('http'); const sinon = require('sinon'); const fs = require('fs'); -const Q = require('q'); const path = require('path'); const at = require('lodash/at'); const uniq = require('lodash/uniq'); @@ -32,6 +31,7 @@ const createTestConfig = require('../../../testUtils/createTestConfig'); const testConstants = require('../../../testUtils/testConstants'); const { shouldTestFeature, DYNAMIC_FOLDERS } = require("../../../spechelper"); +const allSettled = require('../../../testUtils/helpers/allSettled'); const UPLOADER_V2 = cloudinary.v2.uploader; const { @@ -58,7 +58,7 @@ describe("uploader", function () { if (!(config.api_key && config.api_secret)) { expect().fail("Missing key and secret. Please set CLOUDINARY_URL."); } - return Q.allSettled([ + return allSettled([ !cloudinary.config().keep_test_products ? cloudinary.v2.api.delete_resources_by_tag(TEST_TAG) : void 0, !cloudinary.config().keep_test_products ? cloudinary.v2.api.delete_resources_by_tag(TEST_TAG, { @@ -84,8 +84,8 @@ describe("uploader", function () { }); }); it("should successfully upload with metadata", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - uploadImage({ metadata: METADATA_SAMPLE_DATA }); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await uploadImage({ metadata: METADATA_SAMPLE_DATA }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -93,18 +93,15 @@ describe("uploader", function () { }); }); it('should upload a file with correctly encoded transformation string', () => { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - const uploadResult = cloudinary.v2.uploader.upload('irrelevant', { transformation: { overlay: { text: 'test / 火' } } }); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.uploader.upload('irrelevant', { transformation: { overlay: { text: 'test / 火' } } }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('transformation', 'l_text:test %2F 火'))); }); }); it('should upload a file with correctly encoded transformation string incl 4bytes characters', () => { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.uploader.upload('irrelevant', { transformation: { overlay: { text: 'test 𩸽 🍺' } } }) - .then((uploadResult) => { - sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('transformation', 'l_text:test 𩸽 🍺'))); - expect(uploadResult).to.have.key("created_at"); - }); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.uploader.upload('irrelevant', { transformation: { overlay: { text: 'test 𩸽 🍺' } } }).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('transformation', 'l_text:test 𩸽 🍺'))); }); }); it("should successfully upload url", function () { @@ -160,23 +157,23 @@ describe("uploader", function () { }); describe("remote urls ", function () { const mocked = helper.mockTest(); - it("should send s3:// URLs to server", function () { - cloudinary.v2.uploader.upload("s3://test/1.jpg", { + it("should send s3:// URLs to server", async function () { + await cloudinary.v2.uploader.upload("s3://test/1.jpg", { tags: UPLOAD_TAGS - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "s3://test/1.jpg"))); }); - it("should send gs:// URLs to server", function () { - cloudinary.v2.uploader.upload("gs://test/1.jpg", { + it("should send gs:// URLs to server", async function () { + await cloudinary.v2.uploader.upload("gs://test/1.jpg", { tags: UPLOAD_TAGS - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "gs://test/1.jpg"))); }); - it("should send ftp:// URLs to server", function () { - cloudinary.v2.uploader.upload("ftp://example.com/1.jpg", { + it("should send ftp:// URLs to server", async function () { + await cloudinary.v2.uploader.upload("ftp://test/1.jpg", { tags: UPLOAD_TAGS - }); - sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "ftp://example.com/1.jpg"))); + }).catch(helper.ignoreApiFailure); + sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "ftp://test/1.jpg"))); }); }); describe("rename", function () { @@ -219,8 +216,8 @@ describe("uploader", function () { expect(renameResult).to.have.property('context'); }); it('should include notification_url in rename response if included in the request', async () => { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - const renameResult = cloudinary.v2.uploader.rename('irrelevant', 'irrelevant', { notification_url: 'https://notification-url.com' }); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.uploader.rename('irrelevant', 'irrelevant', { notification_url: 'https://notification-url.com' }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('notification_url', 'https://notification-url.com'))); }); }); @@ -236,10 +233,10 @@ describe("uploader", function () { spy.restore(); return xhr.restore(); }); - it("should pass the invalidate value in rename to the server", function () { - cloudinary.v2.uploader.rename("first_id", "second_id", { + it("should pass the invalidate value in rename to the server", async function () { + await cloudinary.v2.uploader.rename("first_id", "second_id", { invalidate: true - }); + }).catch(helper.ignoreApiFailure); expect(spy.calledWith(sinon.match(function (arg) { return arg.toString().match(/name="invalidate"/); }))).to.be.ok(); @@ -263,8 +260,8 @@ describe("uploader", function () { }); }); it('should pass notification_url', async () => { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - const renameResult = cloudinary.v2.uploader.destroy('irrelevant', { notification_url: 'https://notification-url.com' }); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.uploader.destroy('irrelevant', { notification_url: 'https://notification-url.com' }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('notification_url', 'https://notification-url.com'))); }); }); @@ -320,12 +317,12 @@ describe("uploader", function () { }); describe("extra headers", function () { it("should support extra headers in object format e.g. {Link: \"1\"}", function () { - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.uploader.upload(IMAGE_FILE, { + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.uploader.upload(IMAGE_FILE, { extra_headers: { Link: "1" } - }); + }).catch(helper.ignoreApiFailure); assert.ok(requestSpy.args[0][0].headers.Link); assert.equal(requestSpy.args[0][0].headers.Link, "1"); }); @@ -437,11 +434,10 @@ describe("uploader", function () { }); describe("context", function () { this.timeout(TIMEOUT.MEDIUM); - before(function () { - return Q.all([uploadImage(), uploadImage()]).spread((result1, result2) => { - this.first_id = result1.public_id; - this.second_id = result2.public_id; - }); + before(async function () { + const [result1, result2] = await Promise.all([uploadImage(), uploadImage()]); + this.first_id = result1.public_id; + this.second_id = result2.public_id; }); it("should add context to existing resources", function () { return cloudinary.v2.uploader @@ -704,10 +700,10 @@ describe("uploader", function () { return xhr.restore(); }); - it('should pass its value to the upload api', () => { - cloudinary.v2.uploader.upload(IMAGE_FILE, { + it('should pass its value to the upload api', async () => { + await cloudinary.v2.uploader.upload(IMAGE_FILE, { on_success: 'current_asset.update({tags: ["autocaption"]});' - }); + }).catch(helper.ignoreApiFailure); expect(spy.calledWith(sinon.match((arg) => { return arg.toString().match(/on_success='current_asset.update({tags: ["autocaption"]});'/); @@ -737,7 +733,8 @@ describe("uploader", function () { fs.stat(LARGE_RAW_FILE, function (err, stat) { cloudinary.v2.uploader.upload_large(LARGE_RAW_FILE, { chunk_size: 40000, - tags: UPLOAD_TAGS + tags: UPLOAD_TAGS, + disable_promises: true }, function (error, result) { expect(error.message).to.eql("All parts except EOF-chunk must be larger than 5mb"); done(); @@ -798,42 +795,45 @@ describe("uploader", function () { }); }); }); - it("should support uploading large video files", function () { + it("should support uploading large video files", function (done) { var stat, writeSpy; this.timeout(TIMEOUT.LONG * 10); writeSpy = sinon.spy(ClientRequest.prototype, 'write'); stat = fs.statSync(LARGE_VIDEO); expect(stat).to.be.ok(); - return Q.denodeify(cloudinary.v2.uploader.upload_chunked)(LARGE_VIDEO, { + cloudinary.v2.uploader.upload_chunked(LARGE_VIDEO, { chunk_size: 6000000, resource_type: 'video', timeout: TIMEOUT.LONG * 10, tags: UPLOAD_TAGS - }).then(function (result) { + }, function (err, result) { var timestamps; - expect(result.bytes).to.eql(stat.size); - expect(result.etag).to.eql("ff6c391d26be0837ee5229885b5bd571"); - timestamps = writeSpy.args.map(function (a) { - return a[0].toString(); - }).filter(function (p) { - return p.match(/timestamp/); - }).map(function (p) { - return p.match(/"timestamp"\s+(\d+)/)[1]; - }); - expect(timestamps.length).to.be.greaterThan(1); - expect(uniq(timestamps)).to.eql(uniq(timestamps)); // uniq b/c last timestamp may be duplicated - }).finally(function () { - writeSpy.restore(); + try { + expect(result.bytes).to.eql(stat.size); + expect(result.etag).to.eql("ff6c391d26be0837ee5229885b5bd571"); + timestamps = writeSpy.args.map(function (a) { + return a[0].toString(); + }).filter(function (p) { + return p.match(/timestamp/); + }).map(function (p) { + return p.match(/"timestamp"\s+(\d+)/)[1]; + }); + expect(timestamps.length).to.be.greaterThan(1); + expect(uniq(timestamps)).to.eql(uniq(timestamps)); // uniq b/c last timestamp may be duplicated + } finally { + writeSpy.restore(); + done(); + } }); }); - it("should update timestamp for each chunk", function () { + it("should update timestamp for each chunk", function (done) { var writeSpy = sinon.spy(ClientRequest.prototype, 'write'); - return Q.denodeify(cloudinary.v2.uploader.upload_chunked)(LARGE_VIDEO, { + cloudinary.v2.uploader.upload_chunked(LARGE_VIDEO, { chunk_size: 6000000, resource_type: 'video', timeout: TIMEOUT.LONG * 10, tags: UPLOAD_TAGS - }).then(function () { + }, function () { var timestamps = writeSpy.args.map(function (a) { return a[0].toString(); }).filter(function (p) { @@ -841,10 +841,13 @@ describe("uploader", function () { }).map(function (p) { return p.match(/"timestamp"\s+(\d+)/)[1]; }); - expect(timestamps.length).to.be.greaterThan(1); - expect(uniq(timestamps)).to.eql(uniq(timestamps)); - }).finally(function () { - writeSpy.restore(); + try { + expect(timestamps.length).to.be.greaterThan(1); + expect(uniq(timestamps)).to.eql(uniq(timestamps)); + } finally { + writeSpy.restore(); + done(); + } }); }); it("should support uploading based on a url", function (done) { @@ -862,19 +865,19 @@ describe("uploader", function () { }); describe("dynamic folders", () => { const mocked = helper.mockTest(); - it('should pass dynamic folder params', () => { + it('should pass dynamic folder params', async () => { const public_id_prefix = "fd_public_id_prefix"; const asset_folder = "asset_folder"; const display_name = "display_name"; const use_filename_as_display_name = true; const folder = "folder/test"; - UPLOADER_V2.upload(IMAGE_FILE, { + await UPLOADER_V2.upload(IMAGE_FILE, { public_id_prefix, asset_folder, display_name, use_filename_as_display_name, folder - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("public_id_prefix", public_id_prefix)); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("asset_folder", asset_folder)); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("display_name", display_name)); @@ -951,55 +954,46 @@ describe("uploader", function () { }); it("should reject with promise rejection if disable_promises: false", function (done) { - const spy = sinon.spy(); + const unhandledRejectionSpy = sinon.spy(); - cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, { disable_promises: false }, () => { }); + process.on('unhandledRejection', unhandledRejectionSpy); - function unhandledRejection() { - spy(); - } - process.on('unhandledRejection', unhandledRejection); + cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, { disable_promises: false }, () => { }); // Promises are not disabled meaning we should throw unhandledRejection setTimeout(() => { - expect(sinon.assert.called(spy)); - process.removeListener('unhandledRejection', unhandledRejection); + expect(sinon.assert.called(unhandledRejectionSpy)); + process.removeListener('unhandledRejection', unhandledRejectionSpy); done(); }, 2000); }); it("should reject with promise rejection by default", function (done) { - const spy = sinon.spy(); + const unhandledRejectionSpy = sinon.spy(); - cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, () => { }); - function unhandledRejection() { - spy(); - } - process.on('unhandledRejection', unhandledRejection); + process.on('unhandledRejection', unhandledRejectionSpy); + + cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, () => { }); // Promises are not disabled meaning we should throw unhandledRejection setTimeout(() => { - expect(sinon.assert.called(spy)); - process.removeListener('unhandledRejection', unhandledRejection); + expect(sinon.assert.called(unhandledRejectionSpy)); + process.removeListener('unhandledRejection', unhandledRejectionSpy); done(); }, 2000); }); it("should reject without promise rejection if disable_promises: true", function (done) { - const spy = sinon.spy(); + const unhandledRejectionSpy = sinon.spy(); + process.on('unhandledRejection', unhandledRejectionSpy); cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, { disable_promises: true }, () => { }); - function unhandledRejection() { - spy(); - } - process.on('unhandledRejection', unhandledRejection); - // Promises are disabled meaning unhandledRejection was not called setTimeout(() => { - expect(sinon.assert.notCalled(spy)); - process.removeListener('unhandledRejection', unhandledRejection); + expect(sinon.assert.notCalled(unhandledRejectionSpy)); + process.removeListener('unhandledRejection', unhandledRejectionSpy); done(); }, 2000); }); @@ -1125,8 +1119,8 @@ describe("uploader", function () { }); describe("async upload", function () { var mocked = helper.mockTest(); - it("should pass `async` value to the server", function () { - cloudinary.v2.uploader.upload(IMAGE_FILE, { + it("should pass `async` value to the server", async function () { + await cloudinary.v2.uploader.upload(IMAGE_FILE, { async: true, transformation: { effect: "sepia" @@ -1136,9 +1130,9 @@ describe("uploader", function () { }); }); it("should pass `accessibility_analysis` option to the server", function () { - return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => { - cloudinary.v2.uploader.upload(IMAGE_FILE, { accessibility_analysis: true }); - return sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher("accessibility_analysis", 1))); + return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { + await cloudinary.v2.uploader.upload(IMAGE_FILE, { accessibility_analysis: true }); + sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher("accessibility_analysis", 1))); }); }); describe("explicit", function () { @@ -1154,8 +1148,8 @@ describe("uploader", function () { xhr.restore(); }); describe(":invalidate", function () { - it("should pass the invalidate value to the server", function () { - cloudinary.v2.uploader.explicit("cloudinary", { + it("should pass the invalidate value to the server", async function () { + await cloudinary.v2.uploader.explicit("cloudinary", { type: "twitter_name", eager: [ { @@ -1166,24 +1160,24 @@ describe("uploader", function () { invalidate: true, quality_analysis: true, tags: [TEST_TAG] - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('invalidate', 1))); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('quality_analysis', 1))); }); }); - it("should support metadata", function () { - cloudinary.v2.uploader.explicit("cloudinary", { metadata: METADATA_SAMPLE_DATA }); + it("should support metadata", async function () { + await cloudinary.v2.uploader.explicit("cloudinary", { metadata: METADATA_SAMPLE_DATA }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher("metadata", METADATA_SAMPLE_DATA_ENCODED))); }); - it("should support raw_convert", function () { - cloudinary.v2.uploader.explicit("cloudinary", { + it("should support raw_convert", async function () { + await cloudinary.v2.uploader.explicit("cloudinary", { raw_convert: "google_speech", tags: [TEST_TAG] - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('raw_convert', 'google_speech'))); }); - it("should pass `accessibility_analysis` to server", function () { - cloudinary.v2.uploader.explicit("cloudinary", { accessibility_analysis: true }); + it("should pass `accessibility_analysis` to server", async function () { + await cloudinary.v2.uploader.explicit("cloudinary", { accessibility_analysis: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('accessibility_analysis', 1))); }); }); @@ -1211,18 +1205,18 @@ describe("uploader", function () { const mocked = helper.mockTest(); const qualityValues = ["auto:advanced", "auto:best", "80:420", "none"]; function testValue(quality) { - return it("should pass '" + quality + "'", function () { - cloudinary.v2.uploader.upload(IMAGE_FILE, { + return it("should pass '" + quality + "'", async function () { + await cloudinary.v2.uploader.upload(IMAGE_FILE, { "quality_override": quality - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("quality_override", quality)); }); } qualityValues.forEach(value => testValue(value)); - it("should be supported by explicit api", function () { - cloudinary.v2.uploader.explicit("cloudinary", { + it("should be supported by explicit api", async function () { + await cloudinary.v2.uploader.explicit("cloudinary", { "quality_override": "auto:best" - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("quality_override", "auto:best")); }); }); @@ -1230,8 +1224,8 @@ describe("uploader", function () { it("should update metadata of existing resources", function () { const metadata_fields = { metadata_color: "red", metadata_shape: "" }; const public_ids = ["test_id_1", "test_id_2"]; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -1243,8 +1237,8 @@ describe("uploader", function () { it("should support updating metadata with clear_invalid", function () { const metadata_fields = { metadata_color: "red" }; const public_ids = ["test_id_1"]; - return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) { - cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids, { clear_invalid: true }); + return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { + await cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids, { clear_invalid: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -1337,11 +1331,10 @@ describe("uploader", function () { external_id: METADATA_FIELD_UNIQUE_EXTERNAL_ID, label: METADATA_FIELD_UNIQUE_EXTERNAL_ID, type: "string" - }).finally(function () { }); + }); }); after(function () { - return cloudinary.v2.api.delete_metadata_field(METADATA_FIELD_UNIQUE_EXTERNAL_ID) - .finally(function () { }); + return cloudinary.v2.api.delete_metadata_field(METADATA_FIELD_UNIQUE_EXTERNAL_ID); }); it("should be set when calling upload with metadata", function () { return uploadImage({ @@ -1363,31 +1356,30 @@ describe("uploader", function () { expect(result.metadata[METADATA_FIELD_UNIQUE_EXTERNAL_ID]).to.eql(METADATA_FIELD_VALUE); }); }); - it('should allow passing both string and a number for a number smd field', () => { + it('should allow passing both string and a number for a number smd field', async () => { const smdNumberField = 'smd_number_field'; - cloudinary.v2.api.add_metadata_field({ + await cloudinary.v2.api.add_metadata_field({ external_id: smdNumberField, label: smdNumberField, - type: 'number' - }).then(() => { - return Promise.all([ - uploadImage({ - tags: UPLOAD_TAGS, - metadata: { - [smdNumberField]: 123 - } - }), - uploadImage({ - tags: UPLOAD_TAGS, - metadata: { - [smdNumberField]: '123' - } - }) - ]); - }).then(([firstUpload, secondUpload]) => { - expect(firstUpload.metadata[smdNumberField]).to.eql(123); - expect(secondUpload.metadata[smdNumberField]).to.eql(123); - }); + type: 'integer' + }) + + const [firstUpload, secondUpload] = await Promise.all([ + uploadImage({ + tags: UPLOAD_TAGS, + metadata: { + [smdNumberField]: 123 + } + }), + uploadImage({ + tags: UPLOAD_TAGS, + metadata: { + [smdNumberField]: '123' + } + }) + ]); + expect(firstUpload.metadata[smdNumberField]).to.eql(123); + expect(secondUpload.metadata[smdNumberField]).to.eql(123); }); it("should be updatable with uploader.update_metadata on an existing resource", function () { let publicId; @@ -1408,18 +1400,16 @@ describe("uploader", function () { let resource_1; let resource_2; - return Q.allSettled( - [ - uploadImage({ - tags: UPLOAD_TAGS - }), - uploadImage({ - tags: UPLOAD_TAGS - }) - ] - ).then(function ([result_1, result_2]) { - resource_1 = result_1.value; - resource_2 = result_2.value; + return Promise.all([ + uploadImage({ + tags: UPLOAD_TAGS + }), + uploadImage({ + tags: UPLOAD_TAGS + }) + ]).then(function ([result_1, result_2]) { + resource_1 = result_1; + resource_2 = result_2; return cloudinary.v2.uploader.update_metadata(metadata_fields, [resource_1.public_id, resource_2.public_id]); }) .then((result) => { @@ -1460,13 +1450,13 @@ describe("uploader", function () { cloudinary.config(configBck2); writeSpy.restore(); }); - it("should allow a signature and timestamp parameter on uploads", function () { - cloudinary.v2.uploader.upload(IMAGE_FILE, { + it("should allow a signature and timestamp parameter on uploads", async function () { + await cloudinary.v2.uploader.upload(IMAGE_FILE, { public_id: 'folder/file', version: '1234', timestamp: 1569707219, signature: 'b77fc0b0dffbf7e74bdad36b615225fb6daff81e' - }); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('signature', "b77fc0b0dffbf7e74bdad36b615225fb6daff81e"))); sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('timestamp', '1569707219'))); }); diff --git a/test/integration/streaming_profiles_spec.js b/test/integration/streaming_profiles_spec.js index fd4fde3a..d4b63f67 100644 --- a/test/integration/streaming_profiles_spec.js +++ b/test/integration/streaming_profiles_spec.js @@ -1,9 +1,9 @@ let describe = require('../testUtils/suite'); const keys = require('lodash/keys'); -const Q = require('q'); const cloudinary = require("../../cloudinary"); const helper = require("../spechelper"); const TIMEOUT = require('../testUtils/testConstants').TIMEOUT; +const allSettled = require('../testUtils/helpers/allSettled'); const api = cloudinary.v2.api; describe('Cloudinary::Api', function () { @@ -17,9 +17,9 @@ describe('Cloudinary::Api', function () { after(function () { cloudinary.config(true); if (cloudinary.config().keep_test_products) { - return Q.resolve(); + return; } - return Q.allSettled([ + return allSettled([ cloudinary.v2.api.delete_streaming_profile(test_id_1), cloudinary.v2.api.delete_streaming_profile(test_id_1 + 'a'), cloudinary.v2.api.delete_streaming_profile(test_id_3) diff --git a/test/spechelper.js b/test/spechelper.js index fd4c3a0d..2c513f67 100644 --- a/test/spechelper.js +++ b/test/spechelper.js @@ -2,14 +2,17 @@ const isFunction = require('lodash/isFunction'); const querystring = require('querystring'); const sinon = require('sinon'); const ClientRequest = require('_http_client').ClientRequest; -const Q = require('q'); const http = require('http'); const https = require('https'); // Load all our custom assertions const cloudinary = require("../cloudinary"); -const { utils, config, Cache } = cloudinary; +const { + utils, + config, + Cache +} = cloudinary; const libPath = 'lib'; const FileKeyValueStorage = require(`../${libPath}/cache/FileKeyValueStorage`); @@ -57,30 +60,30 @@ exports.DYNAMIC_FOLDERS = 'dynamic_folders' const ALL = 'all'; -const { TEST_TAG } = require('./testUtils/testConstants').TAGS; +const {TEST_TAG} = require('./testUtils/testConstants').TAGS; exports.SAMPLE_VIDEO_SOURCES = [ { type: 'mp4', codecs: 'hev1', - transformations: { video_codec: 'h265' } + transformations: {video_codec: 'h265'} }, { type: 'webm', codecs: 'vp9', - transformations: { video_codec: 'vp9' } + transformations: {video_codec: 'vp9'} }, { type: 'mp4', - transformations: { video_codec: 'auto' } + transformations: {video_codec: 'auto'} }, { type: 'webm', - transformations: { video_codec: 'auto' } + transformations: {video_codec: 'auto'} } ]; -exports.test_cloudinary_url = function(public_id, options, expected_url, expected_options) { +exports.test_cloudinary_url = function (public_id, options, expected_url, expected_options) { var url; url = utils.url(public_id, options); expect(url).to.eql(expected_url); @@ -120,13 +123,13 @@ exports.itBehavesLike = function (name, ...args) { }; /** -Create a matcher method for upload parameters -@private -@function helper.paramMatcher -@param {string} name the parameter name -@param {*} value the parameter value -@return {function} the matcher function with the signature (arg)->Boolean -*/ + Create a matcher method for upload parameters + @private + @function helper.paramMatcher + @param {string} name the parameter name + @param {*} value the parameter value + @return {function} the matcher function with the signature (arg)->Boolean + */ exports.uploadParamMatcher = function (name, value) { return function (arg) { var return_part; @@ -137,13 +140,13 @@ exports.uploadParamMatcher = function (name, value) { }; /** - Create a matcher method for api parameters - @private - @function helper.apiParamMatcher - @param {string} name the parameter name - @param {*} value the parameter value - @return {function} the matcher function as (arg)->Boolean -*/ + Create a matcher method for api parameters + @private + @function helper.apiParamMatcher + @param {string} name the parameter name + @param {*} value the parameter value + @return {function} the matcher function as (arg)->Boolean + */ exports.apiParamMatcher = function (name, value) { var expected, params; params = {}; @@ -173,34 +176,34 @@ exports.apiJsonParamMatcher = function (name, value) { }; /** - Escape RegExp characters - @private - @param {string} s the string to escape - @return a new escaped string -*/ + Escape RegExp characters + @private + @param {string} s the string to escape + @return a new escaped string + */ exports.escapeRegexp = function (s) { return s.replace(/[{\[\].*+()}]/g, c => '\\' + c); }; /** -@function mockTest -@nodoc -Provides a wrapper for mocked tests. Must be called in a `describe` context. -@example -
-const mockTest = require('./spechelper').mockTest
-describe("some topic", function() {
- mocked = mockTest()
- it("should do something" function() {
- options.access_control = [acl];
- cloudinary.v2.api.update("id", options);
- sinon.assert.calledWith(mocked.writeSpy, sinon.match(function(arg) {
- return helper.apiParamMatcher('access_control', "[" + acl_string + "]")(arg);
- })
-);
-
-@return {object} the mocked objects: `xhr`, `write`, `request`
-*/
+ @function mockTest
+ @nodoc
+ Provides a wrapper for mocked tests. Must be called in a `describe` context.
+ @example
+
+ const mockTest = require('./spechelper').mockTest
+ describe("some topic", function() {
+ mocked = mockTest()
+ it("should do something" function() {
+ options.access_control = [acl];
+ cloudinary.v2.api.update("id", options);
+ sinon.assert.calledWith(mocked.writeSpy, sinon.match(function(arg) {
+ return helper.apiParamMatcher('access_control', "[" + acl_string + "]")(arg);
+ })
+ );
+
+ @return {object} the mocked objects: `xhr`, `write`, `request`
+ */
exports.mockTest = function () {
var mocked;
mocked = {};
@@ -218,44 +221,55 @@ exports.mockTest = function () {
};
/**
-@callback mockBlock
-A test block
-@param xhr
-@param writeSpy
-@param requestSpy
-@return {*} a promise or a value
-*/
+ @callback mockBlock
+ A test block
+ @param xhr
+ @param writeSpy
+ @param requestSpy
+ @return {*} a promise or a value
+ */
/**
- @function provideMockObjects
- Wraps the function to be mocked using a promise.
- @param {function} providedFunction test function, accepting (mockXHR, writeSpy, requestSpy)
- @return {Promise}
-*/
-exports.provideMockObjects = function (providedFunction) {
+ @function provideMockObjects
+ Wraps the function to be mocked using a promise.
+ @param {function} providedFunction test function, accepting (mockXHR, writeSpy, requestSpy)
+ @return {Promise}
+ */
+exports.provideMockObjects = async function (providedFunction) {
let requestSpy, writeSpy, mockXHR;
- return Q.Promise(function (resolve, reject, notify) {
- var result;
+ var result;
- mockXHR = sinon.useFakeXMLHttpRequest();
- writeSpy = sinon.spy(ClientRequest.prototype, 'write');
- requestSpy = sinon.spy(api_http, 'request');
-
- result = providedFunction(mockXHR, writeSpy, requestSpy);
+ // Restore any existing spies first (safety check for Node 9)
+ if (ClientRequest.prototype.write.restore) {
+ ClientRequest.prototype.write.restore();
+ }
+ if (api_http.request.restore) {
+ api_http.request.restore();
+ }
+ mockXHR = sinon.useFakeXMLHttpRequest();
+ writeSpy = sinon.spy(ClientRequest.prototype, 'write');
+ requestSpy = sinon.spy(api_http, 'request');
- if (result && isFunction(result.then)) {
- return result.then(resolve);
- } else {
- return resolve(result);
- }
- }).finally(function () {
+ try {
+ result = await providedFunction(mockXHR, writeSpy, requestSpy);
+ } finally {
requestSpy.restore();
writeSpy.restore();
mockXHR.restore();
- }).done();
+ }
+ return result;
+};
+
+/**
+ * Ignore expected API failures in tests that only verify request construction.
+ * These tests use provideMockObjects to spy on HTTP requests but still make real API calls.
+ * The API calls may fail (404, auth errors, etc.) which is expected - we only care about
+ * verifying the request parameters, not the response.
+ */
+exports.ignoreApiFailure = () => {
};
exports.setupCache = function () {
@@ -310,7 +324,7 @@ exports.shouldTestAddOn = function (addOn) {
*
* @return boolean
*/
-exports.shouldTestFeature = function(feature){
+exports.shouldTestFeature = function (feature) {
const cldTestFeatures = (process.env.CLD_TEST_FEATURES || '').toLowerCase();
if (cldTestFeatures === ALL) {
return true;
diff --git a/test/testUtils/helpers/allSettled.js b/test/testUtils/helpers/allSettled.js
new file mode 100644
index 00000000..dce308ae
--- /dev/null
+++ b/test/testUtils/helpers/allSettled.js
@@ -0,0 +1,8 @@
+function allSettled(promises) {
+ return Promise.all(
+ promises.map((p = Promise.resolve()) => p.then((value) => ({ status: "fulfilled", value })).catch((reason) => ({ status: "rejected", reason }))
+ )
+ );
+}
+
+module.exports = allSettled;
diff --git a/test/testUtils/helpers/retry.js b/test/testUtils/helpers/retry.js
index 2b8de893..5b61d426 100644
--- a/test/testUtils/helpers/retry.js
+++ b/test/testUtils/helpers/retry.js
@@ -24,6 +24,6 @@ module.exports = async function retry(fn, limit = RETRY.LIMIT, delay= RETRY.DELA
}
// eslint-disable-next-line no-await-in-loop
- await wait(delay)
+ await wait(delay)();
}
}
diff --git a/test/testUtils/helpers/wait.js b/test/testUtils/helpers/wait.js
index 48f9b7a3..bc36bc67 100644
--- a/test/testUtils/helpers/wait.js
+++ b/test/testUtils/helpers/wait.js
@@ -4,10 +4,10 @@
* @return {function(...item): Promise<...item>}
*/
function wait(ms = 0) {
- return (...rest) => {
+ return (value) => {
return new Promise((resolve) => {
setTimeout(() => {
- resolve(...rest);
+ resolve(value);
}, ms);
});
}
diff --git a/test/testUtils/reusableTests/api/toAcceptNextCursor.js b/test/testUtils/reusableTests/api/toAcceptNextCursor.js
index 415fc3fc..a5e6a0e6 100644
--- a/test/testUtils/reusableTests/api/toAcceptNextCursor.js
+++ b/test/testUtils/reusableTests/api/toAcceptNextCursor.js
@@ -4,10 +4,10 @@ const helper = require("../../../spechelper");
registerReusableTest("accepts next_cursor", function (testFunc, ...args) {
it("Has a next cursor", function () {
- return helper.provideMockObjects((mockXHR, writeSpy, requestSpy) => {
- testFunc(...args, {
+ return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
+ await testFunc(...args, {
next_cursor: 23452342
- });
+ }).catch(helper.ignoreApiFailure);
// TODO Why aren't we sure what's called here?
if (writeSpy.called) {
diff --git a/test/testUtils/reusableTests/api/toBeACursor.js b/test/testUtils/reusableTests/api/toBeACursor.js
index 1a1fe9bf..39b4dcbf 100644
--- a/test/testUtils/reusableTests/api/toBeACursor.js
+++ b/test/testUtils/reusableTests/api/toBeACursor.js
@@ -4,10 +4,10 @@ const helper = require("../../../spechelper");
registerReusableTest("a list with a cursor", function (testFunc, ...args) {
it("Cursor has max results", function () {
- return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) {
- testFunc(...args, {
+ return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) {
+ await testFunc(...args, {
max_results: 10
- });
+ }).catch(helper.ignoreApiFailure);
// TODO why don't we know what is used?
if (writeSpy.called) {
@@ -20,10 +20,10 @@ registerReusableTest("a list with a cursor", function (testFunc, ...args) {
});
});
it("Cursor has next cursor", function () {
- return helper.provideMockObjects(function (mockXHR, writeSpy, requestSpy) {
- testFunc(...args, {
+ return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) {
+ await testFunc(...args, {
next_cursor: 23452342
- });
+ }).catch(helper.ignoreApiFailure);
// TODO why don't we know what is used?
if (writeSpy.called) {
diff --git a/test/testUtils/suite.js b/test/testUtils/suite.js
index a1b2bfcb..f49c0e94 100644
--- a/test/testUtils/suite.js
+++ b/test/testUtils/suite.js
@@ -12,4 +12,7 @@ function makeSuite(name, tests) {
});
}
+makeSuite.only = describe.only;
+makeSuite.skip = describe.skip;
+
module.exports = makeSuite;
diff --git a/test/unit/api_restore_spec.js b/test/unit/api_restore_spec.js
index e6fd097b..04ffe95a 100644
--- a/test/unit/api_restore_spec.js
+++ b/test/unit/api_restore_spec.js
@@ -24,14 +24,14 @@ describe('api restore handlers', function () {
});
describe('.restore', function () {
- it('sends public_ids and versions with JSON payload', function () {
+ it('sends public_ids and versions with JSON payload', async function () {
const options = {
resource_type: 'video',
type: 'authenticated',
versions: ['ver-1', 'ver-2']
};
- cloudinary.v2.api.restore(['pub-1', 'pub-2'], options);
+ await cloudinary.v2.api.restore(['pub-1', 'pub-2'], options).catch(helper.ignoreApiFailure);
sinon.assert.calledWith(mocked.request, sinon.match({
pathname: sinon.match('resources/video/authenticated/restore'),
@@ -44,11 +44,11 @@ describe('api restore handlers', function () {
});
describe('.restore_by_asset_ids', function () {
- it('sends asset_ids and versions with JSON payload', function () {
+ it('sends asset_ids and versions with JSON payload', async function () {
const options = { versions: ['ver-3'] };
const assetIds = ['asset-1', 'asset-2'];
- cloudinary.v2.api.restore_by_asset_ids(assetIds, options);
+ await cloudinary.v2.api.restore_by_asset_ids(assetIds, options).catch(helper.ignoreApiFailure);
sinon.assert.calledWith(mocked.request, sinon.match({
pathname: sinon.match('resources/restore'),
@@ -59,8 +59,8 @@ describe('api restore handlers', function () {
sinon.assert.calledWith(mocked.write, sinon.match(helper.apiJsonParamMatcher('versions', ['ver-3'])));
});
- it('wraps a single asset id into an array before calling the API', function () {
- cloudinary.v2.api.restore_by_asset_ids('single-asset-id');
+ it('wraps a single asset id into an array before calling the API', async function () {
+ await cloudinary.v2.api.restore_by_asset_ids('single-asset-id').catch(helper.ignoreApiFailure);
sinon.assert.calledWith(mocked.request, sinon.match({
pathname: sinon.match('resources/restore'),
diff --git a/test/unit/q_polyfill_spec.js b/test/unit/q_polyfill_spec.js
new file mode 100644
index 00000000..da37078b
--- /dev/null
+++ b/test/unit/q_polyfill_spec.js
@@ -0,0 +1,153 @@
+const expect = require('expect.js');
+const sinon = require('sinon');
+
+const cloudinary = require('../../cloudinary');
+const utils = require('../../lib/utils');
+
+describe('Q compatibility shim for native promises', function () {
+ let nativeFinally;
+
+ beforeEach(function () {
+ nativeFinally = Promise.prototype.finally;
+ // eslint-disable-next-line no-extend-native
+ Promise.prototype.finally = undefined;
+ });
+
+ afterEach(function () {
+ if (nativeFinally) {
+ // eslint-disable-next-line no-extend-native
+ Promise.prototype.finally = nativeFinally;
+ } else {
+ delete Promise.prototype.finally;
+ }
+ });
+
+ it('adds finally/fin helpers and keeps chained promises patched', async function () {
+ const deferred = utils.deferredPromise();
+ const spy = sinon.spy();
+ const chained = deferred.promise.finally(spy).then((value) => value);
+
+ deferred.resolve('ok');
+
+ const value = await chained;
+
+ expect(value).to.be('ok');
+ expect(spy.calledOnce).to.be(true);
+ expect(chained.finally).to.be.a('function');
+ expect(deferred.promise.fin).to.be.a('function');
+ });
+
+ it('adds fail as an alias of catch and allows chaining finally afterwards', async function () {
+ const deferred = utils.deferredPromise();
+ const fallback = sinon.stub().returns('fallback');
+ const failed = deferred.promise.fail(fallback).finally(() => {});
+
+ deferred.reject(new Error('boom'));
+
+ const value = await failed;
+
+ expect(value).to.be('fallback');
+ expect(fallback.calledOnce).to.be(true);
+ expect(failed.finally).to.be.a('function');
+ });
+
+ it('supports spread the same way as Q', async function () {
+ const deferred = utils.deferredPromise();
+ const spread = deferred.promise.spread((first, second) => first + second);
+
+ deferred.resolve([2, 3]);
+
+ const value = await spread;
+ expect(value).to.be(5);
+ expect(spread.finally).to.be.a('function');
+ });
+
+ it('supports nodeify callbacks for backwards compatibility', function (done) {
+ const deferred = utils.deferredPromise();
+ const returnValue = deferred.promise.nodeify((err, result) => {
+ expect(err).to.be(null);
+ expect(result).to.be('value');
+ done();
+ });
+
+ deferred.resolve('value');
+
+ expect(returnValue).to.be(deferred.promise);
+ });
+
+ it('passes nodeify errors to the callback', function (done) {
+ const deferred = utils.deferredPromise();
+ const error = new Error('kaput');
+
+ deferred.promise.nodeify((err) => {
+ expect(err).to.be(error);
+ done();
+ });
+
+ deferred.reject(error);
+ });
+
+ it('exposes progress as a no-op and returns the same promise', function () {
+ const deferred = utils.deferredPromise();
+ const result = deferred.promise.progress(() => {});
+
+ expect(result).to.be(deferred.promise);
+ });
+
+ it('surface unhandled rejections through done()', function (done) {
+ const deferred = utils.deferredPromise();
+ const error = new Error('done error');
+ const timer = setTimeout(() => {
+ restoreHandlers();
+ done(new Error('done() did not rethrow the error'));
+ }, 250);
+ const existingHandlers = process.listeners('uncaughtException');
+ const restoreHandlers = () => {
+ process.removeAllListeners('uncaughtException');
+ existingHandlers.forEach((listener) => process.on('uncaughtException', listener));
+ };
+
+ process.removeAllListeners('uncaughtException');
+ process.once('uncaughtException', (err) => {
+ clearTimeout(timer);
+ restoreHandlers();
+ expect(err).to.be(error);
+ done();
+ });
+
+ deferred.promise.done();
+ deferred.reject(error);
+ });
+});
+
+const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
+
+if (nodeMajorVersion === 9) {
+ describe('Node 9 compatibility regression test', function () {
+ let uploadStub;
+
+ beforeEach(function () {
+ uploadStub = sinon.stub(cloudinary.v2.uploader, 'upload').callsFake(() => {
+ const deferred = utils.deferredPromise();
+ setTimeout(() => deferred.resolve({ secure_url: 'demo-image' }), 0);
+ return deferred.promise;
+ });
+ });
+
+ afterEach(function () {
+ uploadStub.restore();
+ });
+
+ it('still exposes finally on uploader promises when the runtime lacks it', function () {
+ expect(typeof Promise.prototype.finally).to.be('undefined');
+ const spy = sinon.spy();
+ const promise = cloudinary.v2.uploader.upload('https://example.com/demo.jpg');
+ const chained = promise.finally(spy);
+
+ return chained.then((result) => {
+ expect(result.secure_url).to.be('demo-image');
+ expect(spy.calledOnce).to.be(true);
+ });
+ });
+ });
+}
diff --git a/test/utils/utils_spec.js b/test/utils/utils_spec.js
index b4d8c4d3..c6108ce7 100644
--- a/test/utils/utils_spec.js
+++ b/test/utils/utils_spec.js
@@ -797,7 +797,7 @@ describe("utils", function () {
}) {
layers_options.forEach(function ([name, layer, result]) {
it(`should support ${name} ${param}`, function () {
- expect(["test", {[param]: layer}]).to.produceUrl(`https://res.cloudinary.com/${cloud_name}/image/upload/${letter}_${result}/test`).and.emptyOptions();
+ expect(["test", { [param]: layer }]).to.produceUrl(`https://res.cloudinary.com/${cloud_name}/image/upload/${letter}_${result}/test`).and.emptyOptions();
});
});
it(`should not pass width/height to html for ${param}`, function () {
@@ -845,12 +845,12 @@ describe("utils", function () {
});
describe('fps', function () {
[
- [{fps: "24-29.97"}, "fps_24-29.97"],
- [{fps: 24}, "fps_24"],
- [{fps: 24.5}, "fps_24.5"],
- [{fps: "24"}, "fps_24"],
- [{fps: "-24"}, "fps_-24"],
- [{fps: [24, 29.97]}, "fps_24-29.97"]
+ [{ fps: "24-29.97" }, "fps_24-29.97"],
+ [{ fps: 24 }, "fps_24"],
+ [{ fps: 24.5 }, "fps_24.5"],
+ [{ fps: "24" }, "fps_24"],
+ [{ fps: "-24" }, "fps_-24"],
+ [{ fps: [24, 29.97] }, "fps_24-29.97"]
].forEach(function ([option, expected]) {
expect(cloudinary.utils.generate_transformation_string(option)).to.eql(expected);
});
@@ -1067,6 +1067,14 @@ describe("utils", function () {
var fileName, srt;
// Reset, in case some other test mutated the config (which happens...)
cloudinary.config(true);
+
+ // Upload sample image for overlay tests
+ await cloudinary.v2.uploader.upload(helper.IMAGE_FILE, {
+ public_id: "sample",
+ overwrite: true,
+ tags: TEST_TAG
+ });
+
// This is used by all tests
await cloudinary.v2.uploader.text(text_layer, {
public_id: "test_text",
@@ -1184,7 +1192,7 @@ describe("utils", function () {
expect(["sample", opt]).to.produceUrl(`https://res.cloudinary.com/${cloud_name}/image/upload/l_${result}/sample`).and.emptyOptions().and.beServedByCloudinary(done);
});
if (!isString(options)) {
- itBehavesLike("a signed url", {overlay: options}, `l_${result}`);
+ itBehavesLike("a signed url", { overlay: options }, `l_${result}`);
}
});
it("should not pass width/height to html for overlay", function () {
@@ -1239,19 +1247,19 @@ describe("utils", function () {
[scaled(), sepia()],
'c_scale,h_200,w_100|c_lfill,e_sepia,w_400'],
['should support transformations with multiple components',
- [{transformation: [scaled(), sepia()]}, sepia()],
+ [{ transformation: [scaled(), sepia()] }, sepia()],
'c_scale,h_200,w_100/c_lfill,e_sepia,w_400|c_lfill,e_sepia,w_400'],
['should concatenate format at the end of the transformation',
- ([scaled({format: 'gif'}), sepia()]),
+ ([scaled({ format: 'gif' }), sepia()]),
'c_scale,h_200,w_100/gif|c_lfill,e_sepia,w_400'],
['should support an empty format',
- ([scaled({format: ''}), sepia()]),
+ ([scaled({ format: '' }), sepia()]),
'c_scale,h_200,w_100/|c_lfill,e_sepia,w_400'],
['should treat a null format as none',
- ([scaled({format: null}), sepia()]),
+ ([scaled({ format: null }), sepia()]),
'c_scale,h_200,w_100|c_lfill,e_sepia,w_400'],
['should concatenate format at the end of the transformation',
- ([scaled({format: 'gif'}), sepia({format: 'jpg'})]),
+ ([scaled({ format: 'gif' }), sepia({ format: 'jpg' })]),
'c_scale,h_200,w_100/gif|c_lfill,e_sepia,w_400/jpg'],
['should support transformations with multiple components and format',
[{
@@ -1332,7 +1340,7 @@ describe("utils", function () {
test_cloudinary_url("folder/test", {},
`https://res.cloudinary.com/${cloud_name}/image/upload/v1/folder/test`, {});
test_cloudinary_url("folder/test",
- {force_version: false}, `https://res.cloudinary.com/${cloud_name}/image/upload/folder/test`, {});
+ { force_version: false }, `https://res.cloudinary.com/${cloud_name}/image/upload/folder/test`, {});
});
it("explicitly set version is always passed", function () {
test_cloudinary_url("test",
@@ -1347,14 +1355,14 @@ describe("utils", function () {
}, `https://res.cloudinary.com/${cloud_name}/image/upload/v1234/folder/test`, {});
});
it("should use force_version from config", function () {
- cloudinary.config({force_version: false});
+ cloudinary.config({ force_version: false });
test_cloudinary_url("folder/test",
{}, `https://res.cloudinary.com/${cloud_name}/image/upload/folder/test`, {});
});
it("should override config with options", function () {
- cloudinary.config({force_version: false});
+ cloudinary.config({ force_version: false });
test_cloudinary_url("folder/test",
- {force_version: true}, `https://res.cloudinary.com/${cloud_name}/image/upload/v1/folder/test`, {});
+ { force_version: true }, `https://res.cloudinary.com/${cloud_name}/image/upload/v1/folder/test`, {});
});
it("should allow to shorted image/upload urls", function () {
test_cloudinary_url("test", {