From ad29309bb8f951ecc7cb6331b1cd49e6ab8d7230 Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Tue, 28 Oct 2025 03:13:25 +0200 Subject: [PATCH 1/5] refactor: use native promises and drop q migrated away from q, and used native promises. unhandled rejections in unit tests now cause the test runner to fail. ensured all rejections are ignored with no-op fn. found and fixed several broken tests that did not report failures correctly. fixed wait() not used correctly outside .then, as it's returning a higher order fn rather than the promise (to chain values in .then callbacks) implemented a tiny test polyfill for Promise.allSettled to support older Node versions. worked around a timezone issue and a test failing when running it in ~1am locally, heh. added ability to use .only/skip on the test proxy of mocha's describe fn --- lib/api_client/execute_request.js | 3 +- lib/uploader.js | 3 +- lib/utils/index.js | 16 + package.json | 3 +- test/integration/api/admin/api_spec.js | 183 ++++++----- test/integration/api/admin/config_spec.js | 9 +- .../integration/api/admin/folders_api_spec.js | 5 +- .../api/admin/related_assets_spec.js | 33 +- .../api/admin/structured_metadata_spec.js | 63 ++-- test/integration/api/analysis/analyze_spec.js | 11 +- .../authorization/oAuth_authorization_spec.js | 43 +-- test/integration/api/search/search_spec.js | 14 +- .../api/search/visual_search_spec.js | 13 +- test/integration/api/uploader/archivespec.js | 7 +- .../api/uploader/auto_chaptering_spec.js | 9 +- .../api/uploader/auto_transcription_spec.js | 17 +- .../api/uploader/custom_region_spec.js | 25 +- .../api/uploader/slideshow_spec.js | 4 +- .../integration/api/uploader/uploader_spec.js | 305 +++++++++--------- test/integration/streaming_profiles_spec.js | 6 +- test/spechelper.js | 27 +- test/testUtils/helpers/allSettled.js | 8 + test/testUtils/helpers/retry.js | 2 +- test/testUtils/helpers/wait.js | 4 +- .../reusableTests/api/toAcceptNextCursor.js | 7 +- .../reusableTests/api/toBeACursor.js | 13 +- test/testUtils/suite.js | 3 + test/unit/api_restore_spec.js | 13 +- 28 files changed, 432 insertions(+), 417 deletions(-) create mode 100644 test/testUtils/helpers/allSettled.js 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..ab3e83b3 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1652,6 +1652,22 @@ function jsonArrayParam(data, modifier) { */ exports.NOP = function () { }; + +function deferredPromise() { + let resolve, reject + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + 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/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..11934575 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'); @@ -19,7 +18,8 @@ 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, NOP} = require("../../../../lib/utils"); +const allSettled = require('../../../testUtils/helpers/allSettled'); const { TIMEOUT, @@ -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), @@ -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" @@ -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")) })); }); @@ -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, @@ -592,7 +592,7 @@ 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) @@ -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")); }); @@ -837,43 +834,43 @@ describe("api", function () { 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(NOP); 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(NOP); 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(NOP); 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(NOP); 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")); @@ -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(NOP); sinon.assert.calledWith(mocked.write, sinon.match(helper.apiParamMatcher("quality_override", quality))); }); }); @@ -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(NOP); + 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,7 +1228,7 @@ 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')); @@ -1237,8 +1237,8 @@ describe("api", 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,7 +1266,7 @@ 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")); }); @@ -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"))); }); }); @@ -1508,24 +1505,24 @@ describe("api", function () { }); describe("proxy support", function () { const mocked = helper.mockTest(); - it("should support proxy for api calls", function () { + it("should support proxy for api calls", async function () { cloudinary.config({api_proxy: "https://myuser:mypass@example.com"}); - cloudinary.v2.api.resources({}); + await cloudinary.v2.api.resources({}).catch(NOP); sinon.assert.calledWith(mocked.request, sinon.match( arg => arg.agent instanceof https.Agent )); }); - it("should prioritize custom agent", function () { + 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(NOP); 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(NOP); sinon.assert.calledWith(mocked.request, sinon.match( arg => arg.agent instanceof https.Agent )); diff --git a/test/integration/api/admin/config_spec.js b/test/integration/api/admin/config_spec.js index b0c2ecc8..b99d0511 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 { NOP } = require('../../../../lib/utils'); 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(NOP); 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(NOP); 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..e1e7b1fb 100644 --- a/test/integration/api/admin/folders_api_spec.js +++ b/test/integration/api/admin/folders_api_spec.js @@ -5,6 +5,7 @@ const cloudinary = require('../../../../lib/cloudinary'); const createTestConfig = require('../../../testUtils/createTestConfig'); const helper = require('../../../spechelper'); const api_http = require("https"); +const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Admin API - Folders', () => { @@ -23,8 +24,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(NOP); 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..f734f3c0 100644 --- a/test/integration/api/admin/related_assets_spec.js +++ b/test/integration/api/admin/related_assets_spec.js @@ -5,6 +5,7 @@ const { deepStrictEqual } = require('assert'); const {TEST_CLOUD_NAME} = require('../../../testUtils/testConstants'); +const { NOP } = require('../../../../lib/utils'); describe('Asset relations API', () => { const testPublicId = 'test-public-id'; @@ -15,8 +16,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -27,8 +28,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -41,8 +42,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -53,8 +54,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -69,8 +70,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -81,8 +82,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -95,8 +96,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -107,8 +108,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(NOP); 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..a3525ada 100644 --- a/test/integration/api/admin/structured_metadata_spec.js +++ b/test/integration/api/admin/structured_metadata_spec.js @@ -1,9 +1,10 @@ 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 { NOP } = require('../../../../lib/utils'); +const allSettled = require('../../../testUtils/helpers/allSettled'); const TEST_ID = Date.now(); @@ -87,7 +88,7 @@ function createMetadataFieldForTest(field) { if (!field.label) { field.label = field.external_id; } - return api.add_metadata_field(field); + return api.add_metadata_field(field).catch(NOP); } describe("structured metadata api", function () { @@ -95,25 +96,23 @@ describe("structured metadata api", function () { before(function () { // Create the metadata fields required for the tests - return Q.allSettled( + return allSettled( metadata_fields_to_create.map(field => createMetadataFieldForTest(field)) - ).finally(function () { - }); + ); }); after(function () { // Delete all metadata fields created during testing - return Q.allSettled( + 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") @@ -134,13 +133,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 +150,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 +183,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 +192,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") @@ -280,9 +279,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(NOP); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match("DELETE") @@ -446,8 +445,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 +459,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 +473,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), @@ -531,8 +530,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(NOP); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match('GET') @@ -542,14 +541,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(NOP); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -570,7 +569,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 +577,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(NOP); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -600,8 +599,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(NOP); 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..b28b0e4a 100644 --- a/test/integration/api/analysis/analyze_spec.js +++ b/test/integration/api/analysis/analyze_spec.js @@ -5,6 +5,7 @@ const api_http = require('https'); const cloudinary = require('../../../../cloudinary'); const helper = require('../../../spechelper'); +const { NOP } = require('../../../../lib/utils'); describe('Analyze API', () => { describe('uri analysis', () => { @@ -25,8 +26,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(NOP); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match(new RegExp(`/v2/${config.cloud_name}/analysis/analyze/uri`)), @@ -36,11 +37,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(NOP); 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..ec5b12d7 100644 --- a/test/integration/api/authorization/oAuth_authorization_spec.js +++ b/test/integration/api/authorization/oAuth_authorization_spec.js @@ -3,14 +3,15 @@ const cloudinary = require("../../../../cloudinary"); const helper = require("../../../spechelper"); const describe = require('../../../testUtils/suite'); const testConstants = require('../../../testUtils/testConstants'); +const { NOP } = require('../../../../lib/utils'); const { PUBLIC_IDS } = testConstants; 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(NOP); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -18,14 +19,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(NOP); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -38,9 +39,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(NOP); + sinon.assert.calledWith(requestSpy, sinon.match({ auth: "1234:1234" })); }); }); @@ -67,9 +68,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(NOP); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -77,14 +78,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(NOP); + sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") )); @@ -97,9 +98,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(NOP); + sinon.assert.calledWith(requestSpy, sinon.match({ auth: null })); }); }); @@ -120,9 +121,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(NOP); + 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..44bb85f3 100644 --- a/test/integration/api/search/visual_search_spec.js +++ b/test/integration/api/search/visual_search_spec.js @@ -5,11 +5,12 @@ const { deepStrictEqual } = require('assert'); const {TEST_CLOUD_NAME} = require('../../../testUtils/testConstants'); +const { NOP } = require('../../../../lib/utils'); 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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'GET'); @@ -18,8 +19,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(NOP); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'GET'); @@ -28,8 +29,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(NOP); 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..3091c19b 100644 --- a/test/integration/api/uploader/auto_chaptering_spec.js +++ b/test/integration/api/uploader/auto_chaptering_spec.js @@ -4,6 +4,7 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const createTestConfig = require('../../../testUtils/createTestConfig'); const helper = require('../../../spechelper'); +const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Uploader', () => { @@ -21,15 +22,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(NOP); 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(NOP); 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..09bda434 100644 --- a/test/integration/api/uploader/auto_transcription_spec.js +++ b/test/integration/api/uploader/auto_transcription_spec.js @@ -1,6 +1,7 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const helper = require('../../../spechelper'); +const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Uploader', () => { @@ -18,25 +19,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(NOP); 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(NOP); 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(NOP); 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(NOP); 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..aeb3cdc0 100644 --- a/test/integration/api/uploader/custom_region_spec.js +++ b/test/integration/api/uploader/custom_region_spec.js @@ -4,6 +4,7 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const createTestConfig = require('../../../testUtils/createTestConfig'); const helper = require('../../../spechelper'); +const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Uploader', () => { @@ -21,13 +22,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(NOP); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'box_1': [[1, 2], [3, 4]], @@ -35,13 +36,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(NOP); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], @@ -51,13 +52,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(NOP); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'box_1': [[1, 2], [3, 4]], @@ -65,13 +66,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(NOP); 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..5e9f92d7 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,8 @@ const createTestConfig = require('../../../testUtils/createTestConfig'); const testConstants = require('../../../testUtils/testConstants'); const { shouldTestFeature, DYNAMIC_FOLDERS } = require("../../../spechelper"); +const { NOP } = require('../../../../lib/utils'); +const allSettled = require('../../../testUtils/helpers/allSettled'); const UPLOADER_V2 = cloudinary.v2.uploader; const { @@ -58,7 +59,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 +85,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(NOP); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -93,18 +94,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(NOP); 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(NOP); + sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('transformation', 'l_text:test 𩸽 🍺'))); }); }); it("should successfully upload url", function () { @@ -160,23 +158,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(NOP); 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(NOP); 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(NOP); + sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "ftp://test/1.jpg"))); }); }); describe("rename", function () { @@ -219,8 +217,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(NOP); sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('notification_url', 'https://notification-url.com'))); }); }); @@ -236,10 +234,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(NOP); expect(spy.calledWith(sinon.match(function (arg) { return arg.toString().match(/name="invalidate"/); }))).to.be.ok(); @@ -263,8 +261,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(NOP); sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('notification_url', 'https://notification-url.com'))); }); }); @@ -320,12 +318,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(NOP); assert.ok(requestSpy.args[0][0].headers.Link); assert.equal(requestSpy.args[0][0].headers.Link, "1"); }); @@ -437,11 +435,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 +701,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(NOP); expect(spy.calledWith(sinon.match((arg) => { return arg.toString().match(/on_success='current_asset.update({tags: ["autocaption"]});'/); @@ -737,7 +734,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 +796,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 +842,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 +866,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(NOP); 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 +955,46 @@ describe("uploader", function () { }); it("should reject with promise rejection if disable_promises: false", function (done) { - const spy = sinon.spy(); + const unhandledRejectionSpy = sinon.spy(); + process.on('unhandledRejection', unhandledRejectionSpy); + cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, { disable_promises: false }, () => { }); - function unhandledRejection() { - spy(); - } - process.on('unhandledRejection', unhandledRejection); - // 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, () => { }); + + process.on('unhandledRejection', unhandledRejectionSpy); - function unhandledRejection() { - spy(); - } - process.on('unhandledRejection', unhandledRejection); + 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 +1120,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 +1131,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 +1149,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 +1161,24 @@ describe("uploader", function () { invalidate: true, quality_analysis: true, tags: [TEST_TAG] - }); + }).catch(NOP); 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(NOP); 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(NOP); 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(NOP); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('accessibility_analysis', 1))); }); }); @@ -1211,18 +1206,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(NOP); 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(NOP); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("quality_override", "auto:best")); }); }); @@ -1230,8 +1225,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(NOP); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -1243,8 +1238,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(NOP); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -1337,11 +1332,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 +1357,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 +1401,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 +1451,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(NOP); 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..c41f4b99 100644 --- a/test/spechelper.js +++ b/test/spechelper.js @@ -2,7 +2,6 @@ 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 @@ -233,29 +232,23 @@ A test block @param {function} providedFunction test function, accepting (mockXHR, writeSpy, requestSpy) @return {Promise} */ -exports.provideMockObjects = function (providedFunction) { +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'); + mockXHR = sinon.useFakeXMLHttpRequest(); + writeSpy = sinon.spy(ClientRequest.prototype, 'write'); + requestSpy = sinon.spy(api_http, 'request'); - result = providedFunction(mockXHR, writeSpy, requestSpy); - - - 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; }; exports.setupCache = function () { 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..deadb9ec 100644 --- a/test/testUtils/reusableTests/api/toAcceptNextCursor.js +++ b/test/testUtils/reusableTests/api/toAcceptNextCursor.js @@ -1,13 +1,14 @@ const registerReusableTest = require('../reusableTests').registerReusableTest; const sinon = require('sinon'); const helper = require("../../../spechelper"); +const { NOP } = require('../../../../lib/utils'); 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(NOP); // 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..1f088684 100644 --- a/test/testUtils/reusableTests/api/toBeACursor.js +++ b/test/testUtils/reusableTests/api/toBeACursor.js @@ -1,13 +1,14 @@ const registerReusableTest = require('../reusableTests').registerReusableTest; const sinon = require('sinon'); const helper = require("../../../spechelper"); +const { NOP } = require('../../../../lib/utils'); 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(NOP); // TODO why don't we know what is used? if (writeSpy.called) { @@ -20,10 +21,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(NOP); // 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..f878368f 100644 --- a/test/unit/api_restore_spec.js +++ b/test/unit/api_restore_spec.js @@ -5,6 +5,7 @@ const cloudinary = require('../../lib/cloudinary'); const createTestConfig = require('../testUtils/createTestConfig'); const helper = require('../spechelper'); const api_http = require("https"); +const { NOP } = require('../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('api restore handlers', function () { @@ -24,14 +25,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(NOP); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match('resources/video/authenticated/restore'), @@ -44,11 +45,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(NOP); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match('resources/restore'), @@ -59,8 +60,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(NOP); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match('resources/restore'), From 682e0874b9b971c25eb7ef39f94a59af633f3708 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Mon, 22 Dec 2025 12:58:34 +0100 Subject: [PATCH 2/5] fix: full q backwards compatibility layer --- lib/utils/index.js | 2 + lib/utils/qPolyfill.js | 131 ++++++++++++++++++++++++++++++ test/unit/q_polyfill_spec.js | 153 +++++++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 lib/utils/qPolyfill.js create mode 100644 test/unit/q_polyfill_spec.js diff --git a/lib/utils/index.js b/lib/utils/index.js index ab3e83b3..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 = []; @@ -1659,6 +1660,7 @@ function deferredPromise() { resolve = _resolve; reject = _reject; }); + applyQCompat(promise); return { resolve, reject, 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/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); + }); + }); + }); +} From 8763b837c398fe574928ddc898e007cccad779c7 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Mon, 22 Dec 2025 21:01:54 +0100 Subject: [PATCH 3/5] chore: better naming --- test/integration/api/admin/api_spec.js | 114 +++++++-------- test/integration/api/admin/config_spec.js | 6 +- .../integration/api/admin/folders_api_spec.js | 3 +- .../api/admin/related_assets_spec.js | 17 ++- .../api/admin/structured_metadata_spec.js | 13 +- test/integration/api/analysis/analyze_spec.js | 5 +- .../authorization/oAuth_authorization_spec.js | 15 +- .../api/search/visual_search_spec.js | 7 +- .../api/uploader/auto_chaptering_spec.js | 5 +- .../api/uploader/auto_transcription_spec.js | 9 +- .../api/uploader/custom_region_spec.js | 9 +- .../integration/api/uploader/uploader_spec.js | 51 ++++--- test/spechelper.js | 135 ++++++++++-------- .../reusableTests/api/toAcceptNextCursor.js | 3 +- .../reusableTests/api/toBeACursor.js | 5 +- test/unit/api_restore_spec.js | 7 +- test/utils/utils_spec.js | 42 +++--- 17 files changed, 231 insertions(+), 215 deletions(-) diff --git a/test/integration/api/admin/api_spec.js b/test/integration/api/admin/api_spec.js index 11934575..dc456e1c 100644 --- a/test/integration/api/admin/api_spec.js +++ b/test/integration/api/admin/api_spec.js @@ -14,11 +14,11 @@ 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, NOP} = require("../../../../lib/utils"); +const { only } = require("../../../../lib/utils"); const allSettled = require('../../../testUtils/helpers/allSettled'); const { @@ -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)), []); @@ -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); @@ -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); @@ -412,7 +412,7 @@ describe("api", function () { describe("derived pagination", function () { it("should send the derived_next_cursor to the server", function () { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.resource(PUBLIC_ID, {derived_next_cursor: 'aaa'}); + 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') @@ -422,7 +422,7 @@ describe("api", function () { }); it("should send `accessibility_analysis` param to the server", function () { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.resource(PUBLIC_ID, {accessibility_analysis: true}); + 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); @@ -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); }); @@ -598,7 +598,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); }); @@ -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); }); @@ -816,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 () { @@ -828,7 +828,7 @@ 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 () { @@ -843,7 +843,7 @@ describe("api", function () { }); it("should allow getting a single upload_preset", function () { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.api.upload_preset(API_TEST_UPLOAD_PRESET1).catch(NOP); + await cloudinary.v2.api.upload_preset(API_TEST_UPLOAD_PRESET1).catch(helper.ignoreApiFailure); var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET1 + "$"; sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -853,7 +853,7 @@ describe("api", function () { }); it("should allow deleting upload_presets", function () { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.api.delete_upload_preset(API_TEST_UPLOAD_PRESET2).catch(NOP); + await cloudinary.v2.api.delete_upload_preset(API_TEST_UPLOAD_PRESET2).catch(helper.ignoreApiFailure); var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET2 + "$"; sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -870,7 +870,7 @@ describe("api", function () { disallow_public_id: true, live: true, eval: TEST_EVAL_STR - }).catch(NOP); + }).catch(helper.ignoreApiFailure); var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET3 + "$"; sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -913,8 +913,8 @@ describe("api", function () { }); }); it("should return usage values for a specific date", function () { - const twoDaysAgo = formatDate(subDate(new Date(), {days: 2}), "dd-MM-yyyy"); - return cloudinary.v2.api.usage({date: twoDaysAgo}) + 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"); @@ -930,7 +930,7 @@ describe("api", function () { let options = { keep_original: true }; - await cloudinary.v2.api.delete_all_resources(options).catch(NOP); + 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")); @@ -975,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; @@ -983,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); }); @@ -1048,7 +1048,7 @@ describe("api", function () { const qualityValues = ["auto:advanced", "auto:best", "80:420", "none"]; qualityValues.forEach(quality => { it("should support '" + quality + "' in update", async function () { - await cloudinary.v2.api.update("sample", {quality_override: quality}).catch(NOP); + await cloudinary.v2.api.update("sample", { quality_override: quality }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.write, sinon.match(helper.apiParamMatcher("quality_override", quality))); }); }); @@ -1069,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(); @@ -1082,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 () { @@ -1104,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); @@ -1114,7 +1114,7 @@ describe("api", function () { }); }).then(() => { expect().fail(); - }).catch(function ({error}) { + }).catch(function ({ error }) { expect(error.message).to.contain("Illegal value"); }); }); @@ -1125,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); @@ -1134,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; @@ -1151,7 +1151,7 @@ describe("api", function () { it("should allow the user to define ACL in the update parameters2", function () { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { options.access_control = [acl]; - await cloudinary.v2.api.update("id", options).catch(NOP); + await cloudinary.v2.api.update("id", options).catch(helper.ignoreApiFailure); sinon.assert.calledWith( writeSpy, sinon.match(arg => helper.apiParamMatcher('access_control', `[${acl_string}]`)(arg)) ); @@ -1231,7 +1231,7 @@ describe("api", function () { }).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 () { @@ -1268,7 +1268,7 @@ describe("api", function () { 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 () { @@ -1389,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() ); }); }); @@ -1506,23 +1506,23 @@ describe("api", function () { describe("proxy support", function () { const mocked = helper.mockTest(); it("should support proxy for api calls", async function () { - cloudinary.config({api_proxy: "https://myuser:mypass@example.com"}); - await cloudinary.v2.api.resources({}).catch(NOP); + 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", async function () { - cloudinary.config({api_proxy: "https://myuser:mypass@example.com"}); + cloudinary.config({ api_proxy: "https://myuser:mypass@example.com" }); const custom_agent = https.Agent() - await cloudinary.v2.api.resources({agent: custom_agent}).catch(NOP); + 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", async function () { cloudinary.config({}); - await cloudinary.v2.api.resources({api_proxy: "https://myuser:mypass@example.com"}).catch(NOP); + 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 )); @@ -1531,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) { @@ -1541,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 b99d0511..0b42ac0a 100644 --- a/test/integration/api/admin/config_spec.js +++ b/test/integration/api/admin/config_spec.js @@ -2,7 +2,7 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const api_http = require("https"); -const { NOP } = require('../../../../lib/utils'); +const helper = require('../../../spechelper'); const ClientRequest = require('_http_client').ClientRequest; describe('Admin API - Config', () => { @@ -22,7 +22,7 @@ describe('Admin API - Config', () => { describe('config', () => { it('should send a request to config endpoint', async () => { - await cloudinary.v2.api.config().catch(NOP); + await cloudinary.v2.api.config().catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.request, sinon.match({ pathname: sinon.match('config'), @@ -32,7 +32,7 @@ describe('Admin API - Config', () => { }); it('should send a request to config endpoint with optional parameters', async () => { - await cloudinary.v2.api.config({ settings: true }).catch(NOP); + 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 e1e7b1fb..ceaf7d0f 100644 --- a/test/integration/api/admin/folders_api_spec.js +++ b/test/integration/api/admin/folders_api_spec.js @@ -5,7 +5,6 @@ const cloudinary = require('../../../../lib/cloudinary'); const createTestConfig = require('../../../testUtils/createTestConfig'); const helper = require('../../../spechelper'); const api_http = require("https"); -const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Admin API - Folders', () => { @@ -25,7 +24,7 @@ describe('Admin API - Folders', () => { describe('rename_folder', () => { it('should send a request to update folder endpoint with correct parameters', async () => { - await cloudinary.v2.api.rename_folder('old/path', 'new/path').catch(NOP); + 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 f734f3c0..66f95ba6 100644 --- a/test/integration/api/admin/related_assets_spec.js +++ b/test/integration/api/admin/related_assets_spec.js @@ -5,7 +5,6 @@ const { deepStrictEqual } = require('assert'); const {TEST_CLOUD_NAME} = require('../../../testUtils/testConstants'); -const { NOP } = require('../../../../lib/utils'); describe('Asset relations API', () => { const testPublicId = 'test-public-id'; @@ -17,7 +16,7 @@ describe('Asset relations API', () => { describe('using public id', () => { it('should allow passing a single public id to create a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.add_related_assets(testPublicId, singleRelatedPublicId).catch(NOP); + await cloudinary.v2.api.add_related_assets(testPublicId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -29,7 +28,7 @@ describe('Asset relations API', () => { it('should allow passing multiple public ids to create a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.add_related_assets(testPublicId, multipleRelatedPublicId).catch(NOP); + await cloudinary.v2.api.add_related_assets(testPublicId, multipleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -43,7 +42,7 @@ describe('Asset relations API', () => { describe('using asset id', () => { it('should allow passing a single public id to create a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, singleRelatedPublicId).catch(NOP); + await cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -55,7 +54,7 @@ describe('Asset relations API', () => { it('should allow passing multiple public ids to create a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, multipleRelatedPublicId).catch(NOP); + await cloudinary.v2.api.add_related_assets_by_asset_id(testAssetId, multipleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'POST'); @@ -71,7 +70,7 @@ describe('Asset relations API', () => { describe('using public id', () => { it('should allow passing a single public id to delete a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.delete_related_assets(testPublicId, singleRelatedPublicId).catch(NOP); + await cloudinary.v2.api.delete_related_assets(testPublicId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -83,7 +82,7 @@ describe('Asset relations API', () => { it('should allow passing multiple public ids to delete a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.delete_related_assets(testPublicId, multipleRelatedPublicId).catch(NOP); + await cloudinary.v2.api.delete_related_assets(testPublicId, multipleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -97,7 +96,7 @@ describe('Asset relations API', () => { describe('and using asset id', () => { it('should allow passing a single public id to delete a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.delete_related_assets_by_asset_id(testAssetId, singleRelatedPublicId).catch(NOP); + await cloudinary.v2.api.delete_related_assets_by_asset_id(testAssetId, singleRelatedPublicId).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'DELETE'); @@ -109,7 +108,7 @@ describe('Asset relations API', () => { it('should allow passing multiple public ids to delete a relation', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.delete_related_assets_by_asset_id(testAssetId, multipleRelatedPublicId).catch(NOP); + 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 a3525ada..447817ad 100644 --- a/test/integration/api/admin/structured_metadata_spec.js +++ b/test/integration/api/admin/structured_metadata_spec.js @@ -3,7 +3,6 @@ const sinon = require('sinon'); const cloudinary = require("../../../../cloudinary"); const helper = require("../../../spechelper"); const TIMEOUT = require('../../../testUtils/testConstants').TIMEOUT; -const { NOP } = require('../../../../lib/utils'); const allSettled = require('../../../testUtils/helpers/allSettled'); const TEST_ID = Date.now(); @@ -88,7 +87,7 @@ function createMetadataFieldForTest(field) { if (!field.label) { field.label = field.external_id; } - return api.add_metadata_field(field).catch(NOP); + return api.add_metadata_field(field).catch(helper.ignoreApiFailure); } describe("structured metadata api", function () { @@ -281,7 +280,7 @@ describe("structured metadata api", function () { it("should delete metadata field by external id", function () { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { const expectedPath = `/metadata_fields/${EXTERNAL_ID_DELETE}$`; - await api.delete_metadata_field(EXTERNAL_ID_DELETE).catch(NOP); + 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") @@ -531,7 +530,7 @@ describe("structured metadata api", function () { it('should allow listing metadata rules', () => { const expectedPath = '/metadata_rules'; return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await api.list_metadata_rules().catch(NOP); + await api.list_metadata_rules().catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), method: sinon.match('GET') @@ -548,7 +547,7 @@ describe("structured metadata api", function () { condition: {}, result: {} }; - await api.add_metadata_rule(newMetadataRule).catch(NOP); + await api.add_metadata_rule(newMetadataRule).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), @@ -577,7 +576,7 @@ describe("structured metadata api", function () { result: {}, state: 'inactive' }; - await api.update_metadata_rule('some-metadata-rule-id', ruleUpdate).catch(NOP); + 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,7 +599,7 @@ describe("structured metadata api", function () { it('should allow removing existing metadata rules', () => { const expectedPath = '/metadata_rules/some-metadata-rule-id'; return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await api.delete_metadata_rule('some-metadata-rule-id').catch(NOP); + 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 b28b0e4a..8ebd0899 100644 --- a/test/integration/api/analysis/analyze_spec.js +++ b/test/integration/api/analysis/analyze_spec.js @@ -5,7 +5,6 @@ const api_http = require('https'); const cloudinary = require('../../../../cloudinary'); const helper = require('../../../spechelper'); -const { NOP } = require('../../../../lib/utils'); describe('Analyze API', () => { describe('uri analysis', () => { @@ -27,7 +26,7 @@ describe('Analyze API', () => { }); it('should call analyze endpoint with non-custom analysis_type', async () => { - await cloudinary.analysis.analyze_uri('https://example.com', 'captioning').catch(NOP); + 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`)), @@ -41,7 +40,7 @@ describe('Analyze API', () => { await cloudinary.analysis.analyze_uri('https://example.com', 'custom', { model_name: 'my_model', model_version: 1 - }).catch(NOP); + }).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 ec5b12d7..f8dd1016 100644 --- a/test/integration/api/authorization/oAuth_authorization_spec.js +++ b/test/integration/api/authorization/oAuth_authorization_spec.js @@ -3,14 +3,13 @@ const cloudinary = require("../../../../cloudinary"); const helper = require("../../../spechelper"); const describe = require('../../../testUtils/suite'); const testConstants = require('../../../testUtils/testConstants'); -const { NOP } = require('../../../../lib/utils'); const { PUBLIC_IDS } = testConstants; 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(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.resource(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }).catch(NOP); + 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") @@ -25,7 +24,7 @@ describe("oauth_token", function(){ api_secret: undefined, oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }); - await cloudinary.v2.api.resource(PUBLIC_ID).catch(NOP); + await cloudinary.v2.api.resource(PUBLIC_ID).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") @@ -40,7 +39,7 @@ describe("oauth_token", function(){ oauth_token: undefined }); return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.resource(PUBLIC_ID).catch(NOP); + await cloudinary.v2.api.resource(PUBLIC_ID).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ auth: "1234:1234" })); }); }); @@ -69,7 +68,7 @@ describe("oauth_token", function(){ it("should send the oauth_token option to the server (upload_api)", function() { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.uploader.upload(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }).catch(NOP); + 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") @@ -84,7 +83,7 @@ describe("oauth_token", function(){ api_secret: undefined, oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }); - await cloudinary.v2.uploader.upload(PUBLIC_ID).catch(NOP); + await cloudinary.v2.uploader.upload(PUBLIC_ID).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match.has("headers", sinon.match.has("Authorization", "Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4") @@ -99,7 +98,7 @@ describe("oauth_token", function(){ oauth_token: undefined }); return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.uploader.upload(PUBLIC_ID).catch(NOP); + await cloudinary.v2.uploader.upload(PUBLIC_ID).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ auth: null })); }); }); @@ -122,7 +121,7 @@ describe("oauth_token", function(){ oauth_token: undefined }); return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.uploader.unsigned_upload(PUBLIC_ID, 'preset').catch(NOP); + 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/visual_search_spec.js b/test/integration/api/search/visual_search_spec.js index 44bb85f3..a7f2a7ae 100644 --- a/test/integration/api/search/visual_search_spec.js +++ b/test/integration/api/search/visual_search_spec.js @@ -5,12 +5,11 @@ const { deepStrictEqual } = require('assert'); const {TEST_CLOUD_NAME} = require('../../../testUtils/testConstants'); -const { NOP } = require('../../../../lib/utils'); describe('Visual search', () => { it('should pass the image_url parameter to the api call', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.visual_search({image_url: 'test-image-url'}).catch(NOP); + await cloudinary.v2.api.visual_search({image_url: 'test-image-url'}).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'GET'); @@ -20,7 +19,7 @@ describe('Visual search', () => { it('should pass the image_url parameter to the api call', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.visual_search({image_asset_id: 'image-asset-id'}).catch(NOP); + await cloudinary.v2.api.visual_search({image_asset_id: 'image-asset-id'}).catch(helper.ignoreApiFailure); const [calledWithUrl] = requestSpy.firstCall.args; strictEqual(calledWithUrl.method, 'GET'); @@ -30,7 +29,7 @@ describe('Visual search', () => { it('should pass the image_url parameter to the api call', () => { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { - await cloudinary.v2.api.visual_search({text: 'visual-search-input'}).catch(NOP); + 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/auto_chaptering_spec.js b/test/integration/api/uploader/auto_chaptering_spec.js index 3091c19b..0843cbd0 100644 --- a/test/integration/api/uploader/auto_chaptering_spec.js +++ b/test/integration/api/uploader/auto_chaptering_spec.js @@ -4,7 +4,6 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const createTestConfig = require('../../../testUtils/createTestConfig'); const helper = require('../../../spechelper'); -const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Uploader', () => { @@ -23,14 +22,14 @@ describe('Uploader', () => { describe('upload', () => { it('should send a request with auto_chaptering set to true if requested', async () => { - await cloudinary.v2.uploader.upload('irrelevant', { auto_chaptering: true }).catch(NOP); + 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', async () => { - await cloudinary.v2.uploader.explicit('irrelevant', { auto_chaptering: true }).catch(NOP); + 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 09bda434..93eff4ce 100644 --- a/test/integration/api/uploader/auto_transcription_spec.js +++ b/test/integration/api/uploader/auto_transcription_spec.js @@ -1,7 +1,6 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const helper = require('../../../spechelper'); -const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Uploader', () => { @@ -20,24 +19,24 @@ describe('Uploader', () => { describe('upload', () => { it('should send a request with auto_transcription set to true if requested', async () => { - await cloudinary.v2.uploader.upload('irrelevant', { auto_transcription: true }).catch(NOP); + 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', async () => { - await cloudinary.v2.uploader.upload('irrelevant', { auto_transcription: { translate: ['pl'] } }).catch(NOP); + 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', async () => { - await cloudinary.v2.uploader.explicit('irrelevant', { auto_transcription: true }).catch(NOP); + 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', async () => { - await cloudinary.v2.uploader.explicit('irrelevant', { auto_transcription: { translate: ['pl'] } }).catch(NOP); + 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 aeb3cdc0..074b3aee 100644 --- a/test/integration/api/uploader/custom_region_spec.js +++ b/test/integration/api/uploader/custom_region_spec.js @@ -4,7 +4,6 @@ const sinon = require('sinon'); const cloudinary = require('../../../../lib/cloudinary'); const createTestConfig = require('../../../testUtils/createTestConfig'); const helper = require('../../../spechelper'); -const { NOP } = require('../../../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('Uploader', () => { @@ -28,7 +27,7 @@ describe('Uploader', () => { 'box_1': [[1, 2], [3, 4]], 'box_2': [[5, 6], [7, 8]] } - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'box_1': [[1, 2], [3, 4]], @@ -42,7 +41,7 @@ describe('Uploader', () => { 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], 'custom_2': [[10, 11], [12, 13], [14, 15]] } - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], @@ -58,7 +57,7 @@ describe('Uploader', () => { 'box_1': [[1, 2], [3, 4]], 'box_2': [[5, 6], [7, 8]] } - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('regions', JSON.stringify({ 'box_1': [[1, 2], [3, 4]], @@ -72,7 +71,7 @@ describe('Uploader', () => { 'custom_1': [[1, 2], [3, 4], [5, 6], [7, 8]], 'custom_2': [[10, 11], [12, 13], [14, 15]] } - }).catch(NOP); + }).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/uploader_spec.js b/test/integration/api/uploader/uploader_spec.js index 5e9f92d7..7198b6b7 100644 --- a/test/integration/api/uploader/uploader_spec.js +++ b/test/integration/api/uploader/uploader_spec.js @@ -31,7 +31,6 @@ const createTestConfig = require('../../../testUtils/createTestConfig'); const testConstants = require('../../../testUtils/testConstants'); const { shouldTestFeature, DYNAMIC_FOLDERS } = require("../../../spechelper"); -const { NOP } = require('../../../../lib/utils'); const allSettled = require('../../../testUtils/helpers/allSettled'); const UPLOADER_V2 = cloudinary.v2.uploader; @@ -86,7 +85,7 @@ describe("uploader", function () { }); it("should successfully upload with metadata", function () { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await uploadImage({ metadata: METADATA_SAMPLE_DATA }).catch(NOP); + await uploadImage({ metadata: METADATA_SAMPLE_DATA }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -95,13 +94,13 @@ describe("uploader", function () { }); it('should upload a file with correctly encoded transformation string', () => { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.uploader.upload('irrelevant', { transformation: { overlay: { text: 'test / 火' } } }).catch(NOP); + 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(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.uploader.upload('irrelevant', { transformation: { overlay: { text: 'test 𩸽 🍺' } } }).catch(NOP); + 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 𩸽 🍺'))); }); }); @@ -161,19 +160,19 @@ describe("uploader", function () { it("should send s3:// URLs to server", async function () { await cloudinary.v2.uploader.upload("s3://test/1.jpg", { tags: UPLOAD_TAGS - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "s3://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(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "gs://test/1.jpg"))); }); it("should send ftp:// URLs to server", async function () { await cloudinary.v2.uploader.upload("ftp://test/1.jpg", { tags: UPLOAD_TAGS - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(mocked.write, sinon.match(helper.uploadParamMatcher('file', "ftp://test/1.jpg"))); }); }); @@ -218,7 +217,7 @@ describe("uploader", function () { }); it('should include notification_url in rename response if included in the request', async () => { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.uploader.rename('irrelevant', 'irrelevant', { notification_url: 'https://notification-url.com' }).catch(NOP); + 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'))); }); }); @@ -237,7 +236,7 @@ describe("uploader", function () { 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(NOP); + }).catch(helper.ignoreApiFailure); expect(spy.calledWith(sinon.match(function (arg) { return arg.toString().match(/name="invalidate"/); }))).to.be.ok(); @@ -262,7 +261,7 @@ describe("uploader", function () { }); it('should pass notification_url', async () => { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.uploader.destroy('irrelevant', { notification_url: 'https://notification-url.com' }).catch(NOP); + 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'))); }); }); @@ -323,7 +322,7 @@ describe("uploader", function () { extra_headers: { Link: "1" } - }).catch(NOP); + }).catch(helper.ignoreApiFailure); assert.ok(requestSpy.args[0][0].headers.Link); assert.equal(requestSpy.args[0][0].headers.Link, "1"); }); @@ -704,7 +703,7 @@ describe("uploader", function () { 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(NOP); + }).catch(helper.ignoreApiFailure); expect(spy.calledWith(sinon.match((arg) => { return arg.toString().match(/on_success='current_asset.update({tags: ["autocaption"]});'/); @@ -878,7 +877,7 @@ describe("uploader", function () { display_name, use_filename_as_display_name, folder - }).catch(NOP); + }).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)); @@ -958,7 +957,7 @@ describe("uploader", function () { const unhandledRejectionSpy = sinon.spy(); process.on('unhandledRejection', unhandledRejectionSpy); - + cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, { disable_promises: false }, () => { }); // Promises are not disabled meaning we should throw unhandledRejection @@ -972,7 +971,7 @@ describe("uploader", function () { it("should reject with promise rejection by default", function (done) { const unhandledRejectionSpy = sinon.spy(); - + process.on('unhandledRejection', unhandledRejectionSpy); cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, () => { }); @@ -987,7 +986,7 @@ describe("uploader", function () { it("should reject without promise rejection if disable_promises: true", function (done) { const unhandledRejectionSpy = sinon.spy(); - + process.on('unhandledRejection', unhandledRejectionSpy); cloudinary.v2.uploader.upload_large(EMPTY_IMAGE, { disable_promises: true }, () => { }); @@ -1161,24 +1160,24 @@ describe("uploader", function () { invalidate: true, quality_analysis: true, tags: [TEST_TAG] - }).catch(NOP); + }).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", async function () { - await cloudinary.v2.uploader.explicit("cloudinary", { metadata: METADATA_SAMPLE_DATA }).catch(NOP); + 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", async function () { await cloudinary.v2.uploader.explicit("cloudinary", { raw_convert: "google_speech", tags: [TEST_TAG] - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('raw_convert', 'google_speech'))); }); it("should pass `accessibility_analysis` to server", async function () { - await cloudinary.v2.uploader.explicit("cloudinary", { accessibility_analysis: true }).catch(NOP); + await cloudinary.v2.uploader.explicit("cloudinary", { accessibility_analysis: true }).catch(helper.ignoreApiFailure); sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('accessibility_analysis', 1))); }); }); @@ -1209,7 +1208,7 @@ describe("uploader", function () { return it("should pass '" + quality + "'", async function () { await cloudinary.v2.uploader.upload(IMAGE_FILE, { "quality_override": quality - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("quality_override", quality)); }); } @@ -1217,7 +1216,7 @@ describe("uploader", function () { it("should be supported by explicit api", async function () { await cloudinary.v2.uploader.explicit("cloudinary", { "quality_override": "auto:best" - }).catch(NOP); + }).catch(helper.ignoreApiFailure); sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("quality_override", "auto:best")); }); }); @@ -1226,7 +1225,7 @@ describe("uploader", function () { const metadata_fields = { metadata_color: "red", metadata_shape: "" }; const public_ids = ["test_id_1", "test_id_2"]; return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids).catch(NOP); + await cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids).catch(helper.ignoreApiFailure); sinon.assert.calledWith(requestSpy, sinon.match({ method: sinon.match("POST") })); @@ -1239,7 +1238,7 @@ describe("uploader", function () { const metadata_fields = { metadata_color: "red" }; const public_ids = ["test_id_1"]; return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { - await cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids, { clear_invalid: true }).catch(NOP); + 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") })); @@ -1364,7 +1363,7 @@ describe("uploader", function () { label: smdNumberField, type: 'integer' }) - + const [firstUpload, secondUpload] = await Promise.all([ uploadImage({ tags: UPLOAD_TAGS, @@ -1457,7 +1456,7 @@ describe("uploader", function () { version: '1234', timestamp: 1569707219, signature: 'b77fc0b0dffbf7e74bdad36b615225fb6daff81e' - }).catch(NOP); + }).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/spechelper.js b/test/spechelper.js index c41f4b99..2c513f67 100644 --- a/test/spechelper.js +++ b/test/spechelper.js @@ -8,7 +8,11 @@ const https = require('https'); const cloudinary = require("../cloudinary"); -const { utils, config, Cache } = cloudinary; +const { + utils, + config, + Cache +} = cloudinary; const libPath = 'lib'; const FileKeyValueStorage = require(`../${libPath}/cache/FileKeyValueStorage`); @@ -56,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); @@ -119,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; @@ -136,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 = {}; @@ -172,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 = {}; @@ -217,26 +221,34 @@ 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} -*/ + @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; var result; + // 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'); @@ -251,6 +263,15 @@ exports.provideMockObjects = async function (providedFunction) { 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 () { if (!Cache.getAdapter()) { Cache.setAdapter(new KeyValueCacheAdapter(new FileKeyValueStorage())); @@ -303,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/reusableTests/api/toAcceptNextCursor.js b/test/testUtils/reusableTests/api/toAcceptNextCursor.js index deadb9ec..a5e6a0e6 100644 --- a/test/testUtils/reusableTests/api/toAcceptNextCursor.js +++ b/test/testUtils/reusableTests/api/toAcceptNextCursor.js @@ -1,14 +1,13 @@ const registerReusableTest = require('../reusableTests').registerReusableTest; const sinon = require('sinon'); const helper = require("../../../spechelper"); -const { NOP } = require('../../../../lib/utils'); registerReusableTest("accepts next_cursor", function (testFunc, ...args) { it("Has a next cursor", function () { return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => { await testFunc(...args, { next_cursor: 23452342 - }).catch(NOP); + }).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 1f088684..39b4dcbf 100644 --- a/test/testUtils/reusableTests/api/toBeACursor.js +++ b/test/testUtils/reusableTests/api/toBeACursor.js @@ -1,14 +1,13 @@ const registerReusableTest = require('../reusableTests').registerReusableTest; const sinon = require('sinon'); const helper = require("../../../spechelper"); -const { NOP } = require('../../../../lib/utils'); registerReusableTest("a list with a cursor", function (testFunc, ...args) { it("Cursor has max results", function () { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { await testFunc(...args, { max_results: 10 - }).catch(NOP); + }).catch(helper.ignoreApiFailure); // TODO why don't we know what is used? if (writeSpy.called) { @@ -24,7 +23,7 @@ registerReusableTest("a list with a cursor", function (testFunc, ...args) { return helper.provideMockObjects(async function (mockXHR, writeSpy, requestSpy) { await testFunc(...args, { next_cursor: 23452342 - }).catch(NOP); + }).catch(helper.ignoreApiFailure); // TODO why don't we know what is used? if (writeSpy.called) { diff --git a/test/unit/api_restore_spec.js b/test/unit/api_restore_spec.js index f878368f..04ffe95a 100644 --- a/test/unit/api_restore_spec.js +++ b/test/unit/api_restore_spec.js @@ -5,7 +5,6 @@ const cloudinary = require('../../lib/cloudinary'); const createTestConfig = require('../testUtils/createTestConfig'); const helper = require('../spechelper'); const api_http = require("https"); -const { NOP } = require('../../lib/utils'); const ClientRequest = require('_http_client').ClientRequest; describe('api restore handlers', function () { @@ -32,7 +31,7 @@ describe('api restore handlers', function () { versions: ['ver-1', 'ver-2'] }; - await cloudinary.v2.api.restore(['pub-1', 'pub-2'], options).catch(NOP); + 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'), @@ -49,7 +48,7 @@ describe('api restore handlers', function () { const options = { versions: ['ver-3'] }; const assetIds = ['asset-1', 'asset-2']; - await cloudinary.v2.api.restore_by_asset_ids(assetIds, options).catch(NOP); + 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'), @@ -61,7 +60,7 @@ describe('api restore handlers', function () { }); 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(NOP); + 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/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", { From 861dbbeadf4816d9861cb87dcbb3594ca0bb6177 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Mon, 22 Dec 2025 22:58:53 +0100 Subject: [PATCH 4/5] fix: flakiness --- .../api/admin/structured_metadata_spec.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/integration/api/admin/structured_metadata_spec.js b/test/integration/api/admin/structured_metadata_spec.js index 447817ad..4dabb802 100644 --- a/test/integration/api/admin/structured_metadata_spec.js +++ b/test/integration/api/admin/structured_metadata_spec.js @@ -4,6 +4,7 @@ const cloudinary = require("../../../../cloudinary"); const helper = require("../../../spechelper"); const TIMEOUT = require('../../../testUtils/testConstants').TIMEOUT; const allSettled = require('../../../testUtils/helpers/allSettled'); +const {promises} = require("node:fs"); const TEST_ID = Date.now(); @@ -93,15 +94,14 @@ function createMetadataFieldForTest(field) { describe("structured metadata api", function () { this.timeout(TIMEOUT.LARGE); - before(function () { - // Create the metadata fields required for the tests - return allSettled( - metadata_fields_to_create.map(field => createMetadataFieldForTest(field)) - ); + 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 + // allSettled finishes successfully if all promises finish, even if some reject return allSettled( metadata_fields_external_ids.map(field => api.delete_metadata_field(field)) ); @@ -124,7 +124,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(); }); }); }); @@ -199,7 +199,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 () { @@ -267,7 +267,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(); @@ -299,7 +299,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`); @@ -409,7 +409,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`); @@ -518,7 +518,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(); From fd80e78955c669cab0186d3b1bb4988da5b7eab7 Mon Sep 17 00:00:00 2001 From: cloudinary-pkoniu Date: Mon, 22 Dec 2025 23:02:03 +0100 Subject: [PATCH 5/5] fix: flakiness --- test/integration/api/admin/structured_metadata_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/api/admin/structured_metadata_spec.js b/test/integration/api/admin/structured_metadata_spec.js index 4dabb802..d28e43c1 100644 --- a/test/integration/api/admin/structured_metadata_spec.js +++ b/test/integration/api/admin/structured_metadata_spec.js @@ -4,7 +4,6 @@ const cloudinary = require("../../../../cloudinary"); const helper = require("../../../spechelper"); const TIMEOUT = require('../../../testUtils/testConstants').TIMEOUT; const allSettled = require('../../../testUtils/helpers/allSettled'); -const {promises} = require("node:fs"); const TEST_ID = Date.now();