From be54ab1b5bdb9f5608d28268e9f5603ce64f6ec3 Mon Sep 17 00:00:00 2001 From: RTLcoil Date: Tue, 3 Mar 2020 14:04:19 +0200 Subject: [PATCH 1/2] Align all structured metadata tests with reference implementation --- test/api_spec.js | 145 ++------ test/spechelper.js | 91 ++++- test/structured_metadata_spec.js | 604 +++++++++++++++++-------------- test/uploader_spec.js | 109 ++++++ 4 files changed, 553 insertions(+), 396 deletions(-) diff --git a/test/api_spec.js b/test/api_spec.js index 4b951005..7ec6b543 100644 --- a/test/api_spec.js +++ b/test/api_spec.js @@ -1,4 +1,3 @@ - require('dotenv').load({ silent: true, }); @@ -16,7 +15,6 @@ const itBehavesLike = helper.itBehavesLike; const TEST_TAG = helper.TEST_TAG; const UPLOAD_TAGS = helper.UPLOAD_TAGS; const uploadImage = helper.uploadImage; -const TEST_ID = Date.now(); const SUFFIX = helper.SUFFIX; const PUBLIC_ID_PREFIX = "npm_api_test"; const PUBLIC_ID = PUBLIC_ID_PREFIX + SUFFIX; @@ -44,13 +42,6 @@ const EXPLICIT_TRANSFORMATION2 = { crop: "scale", overlay: `text:Arial_60:${TEST_TAG}`, }; -const METADATA_EXTERNAL_ID_UPLOAD = "metadata_upload_" + TEST_ID; -const METADATA_EXTERNAL_ID_UPDATE = "metadata_uploader_update_" + TEST_ID; -const METADATA_EXTERNAL_ID_EXPLICIT = "metadata_explicit_" + TEST_ID; -const LABEL_INT_1 = 'metadata_label_1_' + TEST_ID; -const LABEL_INT_2 = 'metadata_label_2_' + TEST_ID; -const LABEL_INT_3 = 'metadata_label_3_' + TEST_ID; - sharedExamples("a list with a cursor", function (testFunc, ...args) { specify(":max_results", function () { return helper.mockPromise(function (xhr, writeSpy, requestSpy) { @@ -208,10 +199,11 @@ describe("api", function () { ({ public_id }) => cloudinary.v2.api.resources({ type: "upload" }) .then(result => [public_id, result]) .then(([public_id, result]) => { - let resource = findByAttr(result.resources, "public_id", public_id); - expect(resource).to.be.an(Object); - expect(resource.type).to.eql("upload"); - })); + let resource = findByAttr(result.resources, "public_id", public_id); + expect(resource).to.be.an(Object); + expect(resource.type).to.eql("upload"); + }) + ); }); it("should allow listing resources by prefix", function () { this.timeout(helper.TIMEOUT_MEDIUM); @@ -307,13 +299,13 @@ describe("api", function () { expect(resource.derived).to.have.length(1); }); }); - describe("derived pagination", function(){ - it("should send the derived_next_cursor to the server", function() { + describe("derived pagination", function () { + it("should send the derived_next_cursor to the server", function () { return helper.mockPromise((xhr, writeSpy, requestSpy) => { - cloudinary.v2.api.resource(PUBLIC_ID, {derived_next_cursor: 'aaa'}); + cloudinary.v2.api.resource(PUBLIC_ID, { derived_next_cursor: 'aaa' }); return sinon.assert.calledWith( requestSpy, sinon.match(sinon.match({ - query: sinon.match('derived_next_cursor=aaa') + query: sinon.match('derived_next_cursor=aaa'), }, 'derived_next_cursor=aaa'))); }); }); @@ -627,21 +619,21 @@ describe("api", function () { it("should allow getting a single upload_preset", function () { return helper.mockPromise(function (xhr, write, request) { cloudinary.v2.api.upload_preset(API_TEST_UPLOAD_PRESET1); - var expectedPath="/.*\/upload_presets/"+API_TEST_UPLOAD_PRESET1+"$"; + var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET1 + "$"; return sinon.assert.calledWith(request, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), - method: sinon.match("GET") + method: sinon.match("GET"), })); }); }); it("should allow deleting upload_presets", function () { return helper.mockPromise(function (xhr, write, request) { cloudinary.v2.api.delete_upload_preset(API_TEST_UPLOAD_PRESET2); - var expectedPath="/.*\/upload_presets/"+API_TEST_UPLOAD_PRESET2+"$"; + var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET2 + "$"; return sinon.assert.calledWith(request, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), - method: sinon.match("DELETE") - })) + method: sinon.match("DELETE"), + })); }); }); it("should allow updating upload_presets", function () { @@ -651,16 +643,16 @@ describe("api", function () { colors: true, unsigned: true, disallow_public_id: true, - live: true + live: true, }); - var expectedPath="/.*\/upload_presets/"+API_TEST_UPLOAD_PRESET3+"$"; + var expectedPath = "/.*\/upload_presets/" + API_TEST_UPLOAD_PRESET3 + "$"; sinon.assert.calledWith(request, sinon.match({ pathname: sinon.match(new RegExp(expectedPath)), - method: sinon.match("PUT") + method: sinon.match("PUT"), })); - sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('colors', 1 , "colors=1"))); - sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('unsigned', true , "unsigned=true"))); - sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('disallow_public_id', true , "disallow_public_id=true"))); + sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('colors', 1, "colors=1"))); + sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('unsigned', true, "unsigned=true"))); + sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('disallow_public_id', true, "disallow_public_id=true"))); sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('live', true, "live=true"))); }); }); @@ -670,7 +662,7 @@ describe("api", function () { folder: "upload_folder", unsigned: true, tags: UPLOAD_TAGS, - live: true + live: true, }); sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('unsigned', true, "unsigned=true"))); sinon.assert.calledWith(write, sinon.match(helper.apiParamMatcher('live', true, "live=true"))); @@ -814,85 +806,6 @@ describe("api", function () { }); }); }); - describe("structured metadata fields", function () { - this.timeout(helper.TIMEOUT_LONG); - const METADATA_VALUE = "123456"; - before(function () { - return Q.allSettled( - [ - cloudinary.v2.api.add_metadata_field({ - external_id: METADATA_EXTERNAL_ID_UPDATE, - label: LABEL_INT_1, - type: "string", - }), - cloudinary.v2.api.add_metadata_field({ - external_id: METADATA_EXTERNAL_ID_UPLOAD, - label: LABEL_INT_2, - type: "string", - }), - cloudinary.v2.api.add_metadata_field({ - external_id: METADATA_EXTERNAL_ID_EXPLICIT, - label: LABEL_INT_3, - type: "string", - }), - ] - ).finally(function () {}); - }); - after(function () { - return Q.allSettled( - [ - cloudinary.v2.api.delete_metadata_field(METADATA_EXTERNAL_ID_UPDATE), - cloudinary.v2.api.delete_metadata_field(METADATA_EXTERNAL_ID_UPLOAD), - cloudinary.v2.api.delete_metadata_field(METADATA_EXTERNAL_ID_EXPLICIT), - ] - ).finally(function () {}); - }); - it("should be updatable with uploader.update_metadata", function () { - let publicId; - return uploadImage({ - tags: [TEST_TAG], - }) - .then((result) => { - publicId = result.public_id; - return cloudinary.v2.uploader.update_metadata({ [METADATA_EXTERNAL_ID_UPDATE]: METADATA_VALUE }, [publicId]); - }) - .then((result) => { - expect(result).not.to.be.empty(); - expect(result.public_ids[0]).to.eql(publicId); - return cloudinary.v2.api.resource(publicId); - }) - .then((result) => { - expect(result.metadata[METADATA_EXTERNAL_ID_UPDATE]).to.eql(METADATA_VALUE); - }); - }); - it("should be supported when uploading a resource with metadata", function () { - return uploadImage({ - tags: [TEST_TAG], - metadata: { [METADATA_EXTERNAL_ID_UPLOAD]: METADATA_VALUE }, - }).then((result) => { - expect(result).not.to.be.empty(); - return cloudinary.v2.api.resource(result.public_id); - }).then((result) => { - expect(result.metadata[METADATA_EXTERNAL_ID_UPLOAD]).to.eql(METADATA_VALUE); - }); - }); - it("should be supported when calling explicit with metadata", function () { - return uploadImage({ - tags: [TEST_TAG], - }).then((result) => { - return cloudinary.v2.uploader.explicit(result.public_id, { - type: "upload", - tags: [TEST_TAG], - metadata: { [METADATA_EXTERNAL_ID_EXPLICIT]: METADATA_VALUE }, - }); - }).then(function (result) { - expect(result).not.to.be.empty(); - return cloudinary.v2.api.resource(result.public_id); - }).then((result) => { - expect(result.metadata[METADATA_EXTERNAL_ID_EXPLICIT]).to.eql(METADATA_VALUE); - }); - }); - }); it("should support listing by moderation kind and value", function () { itBehavesLike("a list with a cursor", cloudinary.v2.api.resources_by_moderation, "manual", "approved"); return helper.mockPromise((xhr, write, request) => ["approved", "pending", "rejected"].forEach((stat) => { @@ -962,20 +875,20 @@ describe("api", function () { 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')); }); - describe("delete folders", function() { + describe("delete folders", function () { this.timeout(helper.TIMEOUT_MEDIUM); - const folderPath= "test_folder/delete_folder/"+TEST_TAG; - before(function(){ + const folderPath = "test_folder/delete_folder/" + TEST_TAG; + before(function () { return uploadImage({ folder: folderPath, - tags: UPLOAD_TAGS - }).delay(2 * 1000).then(function() { + tags: UPLOAD_TAGS, + }).delay(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); expect(folder["total_count"]).to.eql(0); expect(folder["folders"]).to.be.empty; - })); + })); }); }); it('should delete an empty folder', function () { @@ -983,8 +896,8 @@ describe("api", function () { return cloudinary.v2.api.delete_folder( folderPath ).delay(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")); + ).then(() => expect().fail() + ).catch(({ error }) => expect(error.message).to.contain("Can't find folder with path")); }); }); describe('.restore', function () { diff --git a/test/spechelper.js b/test/spechelper.js index bd202723..e96aeec8 100644 --- a/test/spechelper.js +++ b/test/spechelper.js @@ -93,13 +93,52 @@ expect.Assertion.prototype.beServedByCloudinary = function (done) { return this; }; +/** + * Asserts that a given object is a datasource. + * + * @returns {expect.Assertion} + */ +expect.Assertion.prototype.beADatasource = function () { + let datasource; + datasource = this.obj; + this.assert('values' in datasource, function () { + return `expected datasource to contain mandatory field: 'values'`; + }, function () { + return `expected datasource not to contain a 'values' field`; + }); + if (!isEmpty(datasource.values)) { + datasource.values.forEach((value) => { + this.assert(typeof value.value === 'string', function () { + return `expected datasource to contain item with mandatory field 'value' type string`; + }, function () { + return `expected datasource not to contain item with mandatory field 'value' type string`; + }); + this.assert(typeof value.external_id === 'string', function () { + return `expected datasource field to contain item with mandatory field: 'value' type string`; + }, function () { + return `expected datasource not to contain item with mandatory field 'external_id' type string`; + }); + if (!isEmpty(value.state)) { + const states = ['active', 'inactive']; + this.assert(includes(states, value.state), function () { + return `expected datasource field state to be one of ${states.join(', ')}. Unknown state ${value.state} received`; + }, function () { + return `expected datasource field state not to be of a certain state`; + }); + } + }); + } + return this; +}; + /** * Asserts that a given object is a metadata field. * Optionally tests the values in the metadata field for equality * + * @param {string} type The type of metadata field we expect * @returns {expect.Assertion} */ -expect.Assertion.prototype.beAMetadataField = function () { +expect.Assertion.prototype.beAMetadataField = function (type = '') { let metadataField, expectedValues; if (Array.isArray(this.obj)) { [metadataField, expectedValues] = this.obj; @@ -123,16 +162,24 @@ expect.Assertion.prototype.beAMetadataField = function () { }, function () { return `expected metadata field of type ${metadataField.type} not to contain a datasource field`; }); + expect(metadataField.datasource).to.beADatasource(); } // Make sure type is acceptable - const acceptableTypes = ['string', 'integer', 'date', 'enum', 'set']; - this.assert(includes(acceptableTypes, metadataField.type), function () { - return `expected metadata field type to be one of ${acceptableTypes.join(', ')}. Unknown field type ${metadataField.type} received`; - }, function () { - return `expected metadata field not to be of a certain type`; - }); - + if (type) { + this.assert(type === metadataField.type, function () { + return `expected metadata field type to equal ${type}`; + }, function () { + return `expected metadata field type ${metadataField.type} not to equal ${type}`; + }); + } else { + const acceptableTypes = ['string', 'integer', 'date', 'enum', 'set']; + this.assert(includes(acceptableTypes, metadataField.type), function () { + return `expected metadata field type to be one of ${acceptableTypes.join(', ')}. Unknown field type ${metadataField.type} received`; + }, function () { + return `expected metadata field not to be of a certain type`; + }); + } // Verify object values if (expectedValues) { Object.entries(expectedValues).forEach(([key, value]) => { @@ -211,6 +258,23 @@ exports.apiParamMatcher = function (name, value) { }; }; +/** + Create a matcher method for api JSON parameters + @private + @function helper.apiJsonParamMatcher + @param {string} name the parameter name + @param {*} value the parameter value + @return {function} the matcher function as (arg)->Boolean + */ +exports.apiJsonParamMatcher = function (name, value) { + return function (arg) { + var expected, jsonArg; + jsonArg = JSON.parse(arg); + expected = JSON.stringify(value); + return jsonArg[name] && JSON.stringify(jsonArg[name]) === expected; + }; +}; + /** Escape RegExp characters @private @@ -306,3 +370,14 @@ exports.setupCache = function () { exports.uploadImage = function (options) { return cloudinary.v2.uploader.upload(exports.IMAGE_FILE, options); }; + +/** + * Convert a timestamp to the date part of an ISO8601 string + * + * @param {string} timestamp The timestamp to convert + * @returns {string} The date part of a ISO8601 date time + */ +exports.toISO8601DateOnly = function (timestamp) { + const date = new Date(timestamp); + return date.toISOString().split('T')[0]; +}; diff --git a/test/structured_metadata_spec.js b/test/structured_metadata_spec.js index a5f9cfa1..e25e81d6 100644 --- a/test/structured_metadata_spec.js +++ b/test/structured_metadata_spec.js @@ -1,356 +1,416 @@ const expect = require("expect.js"); const Q = require('q'); +const sinon = require('sinon'); const cloudinary = require("../cloudinary"); const helper = require("./spechelper"); const TEST_ID = Date.now(); -const TEST_TAG = helper.TEST_TAG; -const UPLOAD_TAGS = helper.UPLOAD_TAGS; -const uploadImage = helper.uploadImage; -const EXTERNAL_ID_CREATE = 'metadata_create_' + TEST_ID; -const EXTERNAL_ID_CREATE_2 = 'metadata_create_2_' + TEST_ID; -const EXTERNAL_ID_DATE_VALIDATION = 'metadata_validate_date_' + TEST_ID; -const EXTERNAL_ID_DATE_VALIDATION_2 = 'metadata_validate_date_2_' + TEST_ID; -const EXTERNAL_ID_GET_LIST = 'metadata_list_' + TEST_ID; -const EXTERNAL_ID_GET_FIELD = 'metadata_get_by_id_' + TEST_ID; -const EXTERNAL_ID_UPDATE_BY_ID = 'metadata_update_by_id_' + TEST_ID; -const EXTERNAL_ID_DELETE = 'metadata_delete_' + TEST_ID; -const EXTERNAL_ID_UPDATE_DATASOURCE = 'metadata_datasource_update_' + TEST_ID; -const EXTERNAL_ID_DELETE_DATASOURCE_ENTRIES = 'metadata_delete_datasource_entries_' + TEST_ID; -const EXTERNAL_ID_RESTORE_DATASOURCE_ENTRIES = 'metadata_restore_datasource_entries_' + TEST_ID; -const EXTERNAL_ID_UPDATE = 'metadata_update_' + TEST_ID; -const PUBLIC_ID_UPLOAD = "metadata_upload_" + TEST_ID; -const LABEL_INT_1 = 'metadata_label_1_' + TEST_ID; -const LABEL_INT_2 = 'metadata_label_2_' + TEST_ID; -const LABEL_INT_3 = 'metadata_label_3_' + TEST_ID; -const LABEL_INT_4 = 'metadata_label_4_' + TEST_ID; -const LABEL_SET_1 = 'metadata_set_1_' + TEST_ID; -const LABEL_SET_2 = 'metadata_set_2_' + TEST_ID; -const LABEL_SET_3 = 'metadata_set_3_' + TEST_ID; -const LABEL_STRING_1 = 'metadata_string_1_' + TEST_ID; -const LABEL_STRING_2 = 'metadata_string_2_' + TEST_ID; -const LABEL_STRING_3 = 'metadata_string_3_' + TEST_ID; -const LABEL_DATE = 'metadata_date_' + TEST_ID; + +const EXTERNAL_ID_GENERAL = 'metadata_external_id_general_' + TEST_ID; +const EXTERNAL_ID_STRING = 'metadata_external_id_string_' + TEST_ID; +const EXTERNAL_ID_INT = 'metadata_external_id_int_' + TEST_ID; +const EXTERNAL_ID_DATE = 'metadata_external_id_date_' + TEST_ID; +const EXTERNAL_ID_ENUM = 'metadata_external_id_enum_' + TEST_ID; +const EXTERNAL_ID_ENUM_2 = 'metadata_external_id_enum_2_' + TEST_ID; +const EXTERNAL_ID_SET = 'metadata_external_id_set_' + TEST_ID; +const EXTERNAL_ID_SET_2 = 'metadata_external_id_set_2_' + TEST_ID; +const EXTERNAL_ID_SET_3 = 'metadata_external_id_set_3_' + TEST_ID; +const EXTERNAL_ID_DELETE = 'metadata_deletion_' + TEST_ID; +const EXTERNAL_ID_DELETE_2 = 'metadata_deletion_2_' + TEST_ID; +const EXTERNAL_ID_DATE_VALIDATION = 'metadata_date_validation_' + TEST_ID; +const EXTERNAL_ID_DATE_VALIDATION_2 = 'metadata_date_validation_2_' + TEST_ID; +const EXTERNAL_ID_INT_VALIDATION = 'metadata_int_validation_' + TEST_ID; +const EXTERNAL_ID_INT_VALIDATION_2 = 'metadata_int_validation_2_' + TEST_ID; +const DATASOURCE_ENTRY_EXTERNAL_ID = 'metadata_datasource_entry_external_id' + TEST_ID; + +const datasource_single = [ + { + value: 'v1', + external_id: DATASOURCE_ENTRY_EXTERNAL_ID, + }, +]; +const datasource_multiple = [ + { + value: 'v2', + external_id: DATASOURCE_ENTRY_EXTERNAL_ID, + }, + { + value: 'v3', + }, + { + value: 'v4', + }, +]; + +const metadata_fields_external_ids = [ + EXTERNAL_ID_GENERAL, EXTERNAL_ID_DATE, EXTERNAL_ID_ENUM_2, EXTERNAL_ID_SET, EXTERNAL_ID_INT_VALIDATION, + EXTERNAL_ID_INT_VALIDATION_2, EXTERNAL_ID_DATE_VALIDATION, EXTERNAL_ID_DATE_VALIDATION_2, +]; + +const metadata_fields_to_create = [ + { + external_id: EXTERNAL_ID_GENERAL, + type: 'string', + }, + { + external_id: EXTERNAL_ID_ENUM_2, + type: 'enum', + datasource: { + values: datasource_multiple, + }, + }, + { + external_id: EXTERNAL_ID_DELETE_2, + type: 'integer', + }, + { + external_id: EXTERNAL_ID_SET_2, + type: 'set', + datasource: { + values: datasource_multiple, + }, + }, + { + external_id: EXTERNAL_ID_SET_3, + type: 'set', + datasource: { + values: datasource_multiple, + }, + }, +]; const api = cloudinary.v2.api; +function createMetadataFieldForTest(field) { + if (!field.label) { + field.label = field.external_id; + } + return api.add_metadata_field(field); +} + describe("structured metadata api", function () { this.timeout(helper.TIMEOUT_LARGE); before(function () { + // Create the metadata fields required for the tests return Q.allSettled( - [ - api.add_metadata_field({ - external_id: EXTERNAL_ID_GET_LIST, - label: LABEL_INT_1, - type: "integer", - default_value: 10, - }), - api.add_metadata_field({ - external_id: EXTERNAL_ID_GET_FIELD, - label: LABEL_INT_2, - type: "integer", - default_value: 1, - }), - api.add_metadata_field({ - external_id: EXTERNAL_ID_UPDATE_BY_ID, - label: LABEL_INT_3, - type: "integer", - default_value: 1, - }), - api.add_metadata_field({ - external_id: EXTERNAL_ID_DELETE, - label: LABEL_INT_4, - type: "integer", - default_value: 6, - }), - api.add_metadata_field({ - external_id: EXTERNAL_ID_UPDATE_DATASOURCE, - label: LABEL_SET_1, - type: "set", - datasource: { - values: [ - { external_id: "color_1", value: "red" }, - { external_id: "color_2", value: "blue" }, - ], - }, - }), - api.add_metadata_field({ - external_id: EXTERNAL_ID_UPDATE, - label: LABEL_STRING_1, - type: "string", - }), - uploadImage({ - public_id: PUBLIC_ID_UPLOAD, - tags: UPLOAD_TAGS, - }), - ] + metadata_fields_to_create.map(field => createMetadataFieldForTest(field)) ).finally(function () {}); }); after(function () { - // Delete all metadata fields created during the test + // Delete all metadata fields created during testing return Q.allSettled( - [ - EXTERNAL_ID_CREATE, - EXTERNAL_ID_CREATE_2, - EXTERNAL_ID_DATE_VALIDATION, - EXTERNAL_ID_DATE_VALIDATION_2, - EXTERNAL_ID_GET_LIST, - EXTERNAL_ID_GET_FIELD, - EXTERNAL_ID_UPDATE_BY_ID, - EXTERNAL_ID_DELETE, - EXTERNAL_ID_UPDATE_DATASOURCE, - EXTERNAL_ID_DELETE_DATASOURCE_ENTRIES, - EXTERNAL_ID_RESTORE_DATASOURCE_ENTRIES, - EXTERNAL_ID_UPDATE, - ].map(field => api.delete_metadata_field(field)) - ).then(function () { - return api.delete_resources_by_tag(TEST_TAG); - }).finally(function () {}); - }); - - describe("add metadata field", function () { - it("should create metadata", function () { - const metadataFields = [ - { - external_id: EXTERNAL_ID_CREATE, - label: LABEL_STRING_2, - type: "string", - default_value: "blue", - }, { - external_id: EXTERNAL_ID_CREATE_2, - label: LABEL_STRING_3, - type: "string", - }, - ]; - return Q.all(metadataFields.map(field => api.add_metadata_field(field))) - .then((results) => { - expect([results[0], metadataFields[0]]).to.beAMetadataField(); - expect([results[1], metadataFields[1]]).to.beAMetadataField(); - return Q.all( - [ - api.metadata_field_by_field_id(EXTERNAL_ID_CREATE), - api.metadata_field_by_field_id(EXTERNAL_ID_CREATE_2), - ] - ); - }) - .then((results) => { - expect([results[0], metadataFields[0]]).to.beAMetadataField(); - expect([results[1], metadataFields[1]]).to.beAMetadataField(); - }); - }); - - describe("date_field_validation", function () { - const maxValidDate = '2000-01-01'; - const minValidDate = '1950-01-01'; - const validDate = '1980-04-20'; - const invalidDate = '1940-01-20'; - const validMetadata = { - external_id: EXTERNAL_ID_DATE_VALIDATION, - label: LABEL_DATE, - type: "date", - mandatory: true, - default_value: validDate, - validation: { - type: "and", - rules: [ - { - type: "greater_than", - value: minValidDate, - }, { - type: "less_than", - value: maxValidDate, - }, - ], - }, - }; - const invalidMetadata = { - ...validMetadata, - external_id: EXTERNAL_ID_DATE_VALIDATION_2, - default_value: invalidDate, - }; - - it("should create date field when default value validation passes", function () { - return api.add_metadata_field(validMetadata) - .then((result) => { - expect(result).to.beAMetadataField(); - return api.metadata_field_by_field_id(EXTERNAL_ID_DATE_VALIDATION); - }) - .then((result) => { - expect(result).to.beAMetadataField(); - expect(result.default_value).to.eql(validMetadata.default_value); - }); - }); - - it("should not create date field with illegal default value", function () { - return api.add_metadata_field(invalidMetadata).then(() => { - expect().fail(); - }).catch((res) => { - expect(res.error).not.to.be(void 0); - expect(res.error.message).to.contain("default_value is invalid"); - }); - }); - }); + 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 () { - return api.list_metadata_fields() - .then((result) => { - expect(result).not.to.be.empty(); - expect(result.metadata_fields).not.to.be.empty(); - expect(result.metadata_fields).to.be.an("array"); - result.metadata_fields.forEach((field) => { - expect(field).to.beAMetadataField(); - }); - }); + const expectedPath = `/metadata_fields$`; + return helper.mockPromise(function (xhr, write, request) { + api.list_metadata_fields(); + sinon.assert.calledWith(request, sinon.match({ + pathname: sinon.match(new RegExp(expectedPath)), + method: sinon.match("GET"), + })); + }); }); }); describe("metadata_field_by_field_id", function () { it("should return metadata field by external id", function () { - return api.metadata_field_by_field_id(EXTERNAL_ID_GET_FIELD) + return api.metadata_field_by_field_id(EXTERNAL_ID_GENERAL) .then((result) => { - expect([result, { external_id: EXTERNAL_ID_GET_FIELD }]).to.beAMetadataField(); + expect([result, { label: EXTERNAL_ID_GENERAL }]).to.beAMetadataField(); }); }); }); + describe("add_metadata_field", function () { + const expectedPath = "/metadata_fields$"; + it("should create string metadata field", function () { + return helper.mockPromise(function (xhr, write, request) { + const metadata = { + external_id: EXTERNAL_ID_STRING, + label: EXTERNAL_ID_STRING, + type: 'string', + }; + api.add_metadata_field(metadata); + sinon.assert.calledWith(request, sinon.match({ + pathname: sinon.match(new RegExp(expectedPath)), + method: sinon.match("POST"), + })); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('external_id', EXTERNAL_ID_STRING))); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('type', 'string'))); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('label', EXTERNAL_ID_STRING))); + }); + }); + it("should create integer metadata field", function () { + return helper.mockPromise(function (xhr, write, request) { + const metadata = { + external_id: EXTERNAL_ID_INT, + label: EXTERNAL_ID_INT, + type: 'integer', + }; + api.add_metadata_field(metadata); + sinon.assert.calledWith(request, sinon.match({ + pathname: sinon.match(new RegExp(expectedPath)), + method: sinon.match("POST"), + })); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('external_id', EXTERNAL_ID_INT))); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('type', 'integer'))); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('label', EXTERNAL_ID_INT))); + }); + }); + it("should create date metadata field", function () { + const metadata = { + external_id: EXTERNAL_ID_DATE, + label: EXTERNAL_ID_DATE, + type: 'date', + }; + return api.add_metadata_field(metadata).then((result) => { + expect(result).to.beAMetadataField(); + return api.metadata_field_by_field_id(EXTERNAL_ID_DATE); + }).then((result) => { + expect([result, { ...metadata, mandatory: false }]).to.beAMetadataField(); + }); + }); + it("should create enum metadata field", function () { + return helper.mockPromise(function (xhr, write, request) { + const metadata = { + datasource: { + values: datasource_single, + }, + external_id: EXTERNAL_ID_ENUM, + label: EXTERNAL_ID_ENUM, + type: 'enum', + }; + api.add_metadata_field(metadata); + sinon.assert.calledWith(request, sinon.match({ + pathname: sinon.match(new RegExp(expectedPath)), + method: sinon.match("POST"), + })); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('external_id', EXTERNAL_ID_ENUM))); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('type', 'enum'))); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('label', EXTERNAL_ID_ENUM))); + sinon.assert.calledWith(write, sinon.match(helper.apiJsonParamMatcher('datasource', { values: datasource_single }))); + }); + }); + it("should create set metadata field", function () { + const metadata = { + datasource: { + values: datasource_multiple, + }, + external_id: EXTERNAL_ID_SET, + label: EXTERNAL_ID_SET, + type: 'set', + }; + return api.add_metadata_field(metadata).then((result) => { + expect(result).to.beAMetadataField(); + return api.metadata_field_by_field_id(EXTERNAL_ID_SET); + }).then((result) => { + expect([result, { + external_id: EXTERNAL_ID_SET, + label: EXTERNAL_ID_SET, + type: 'set', + mandatory: false, + }]).to.beAMetadataField(); + }); + }); + }); + describe("update_metadata_field", function () { it("should update metadata field by external id", function () { - const metadataChanges = { - default_value: 10, + const newLabel = 'update_metadata_test_new_label' + EXTERNAL_ID_GENERAL; + const newDefaultValue = 'update_metadata_test_new_default_value' + EXTERNAL_ID_GENERAL; + const updatedMetadata = { + external_id: EXTERNAL_ID_SET, + label: newLabel, + type: 'integer', + mandatory: true, + default_value: newDefaultValue, }; - return api.update_metadata_field(EXTERNAL_ID_UPDATE_BY_ID, metadataChanges) + return api.update_metadata_field(EXTERNAL_ID_GENERAL, updatedMetadata) .then((result) => { - expect([result, metadataChanges]).to.beAMetadataField(); - return api.metadata_field_by_field_id(EXTERNAL_ID_UPDATE_BY_ID); + expect(result).to.beAMetadataField(); + return api.metadata_field_by_field_id(EXTERNAL_ID_GENERAL); }) .then((result) => { - expect([result, metadataChanges]).to.beAMetadataField(); + expect([result, { + external_id: EXTERNAL_ID_GENERAL, + label: newLabel, + type: 'string', + mandatory: true, + default_value: newDefaultValue, + }]).to.beAMetadataField(); + }); + }); + }); + + 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 }) + .then(() => api.metadata_field_by_field_id(EXTERNAL_ID_ENUM_2)) + .then((result) => { + expect(result.datasource).to.beADatasource(); + expect([result.datasource.values[0], datasource_single[0]]); }); }); }); describe("delete_metadata_field", function () { it("should delete metadata field by external id", function () { - return api.delete_metadata_field(EXTERNAL_ID_DELETE) + return helper.mockPromise(function (xhr, write, request) { + const expectedPath = `/metadata_fields/${EXTERNAL_ID_DELETE}$`; + api.delete_metadata_field(EXTERNAL_ID_DELETE); + sinon.assert.calledWith(request, sinon.match({ + pathname: sinon.match(new RegExp(expectedPath)), + method: sinon.match("DELETE"), + })); + }); + }); + it("should delete metadata field and fail when attempting to create a new one with the same external id", function () { + const metadata = { + external_id: EXTERNAL_ID_DELETE_2, + label: EXTERNAL_ID_DELETE_2, + type: 'integer', + }; + return api.delete_metadata_field(EXTERNAL_ID_DELETE_2) .then((result) => { expect(result).not.to.be.empty(); expect(result.message).to.eql("ok"); - return api.metadata_field_by_field_id(EXTERNAL_ID_DELETE); + return api.add_metadata_field(metadata); }) .catch(({ error }) => { expect(error).not.to.be(void 0); - expect(error.http_code).to.eql(404); - expect(error.message).to.contain(`External ID ${EXTERNAL_ID_DELETE} doesn't exist`); + expect(error.http_code).to.eql(400); + expect(error.message).to.contain(`external id ${EXTERNAL_ID_DELETE_2} already exists`); }); }); }); - describe("update_metadata_field_datasource", function () { - it("should update metadata field datasource by external id", function () { - const datasource_changes = { - values: [ - { external_id: "color_1", value: "brown" }, - { external_id: "color_2", value: "black" }, - ], - }; - return api.update_metadata_field_datasource(EXTERNAL_ID_UPDATE_DATASOURCE, datasource_changes) + describe("delete_datasource_entries", function () { + it("should delete entries in metadata field datasource", function () { + return api.delete_datasource_entries(EXTERNAL_ID_SET_2, [DATASOURCE_ENTRY_EXTERNAL_ID]) .then((result) => { expect(result).not.to.be.empty(); - return api.metadata_field_by_field_id(EXTERNAL_ID_UPDATE_DATASOURCE); + expect(result).to.beADatasource(); + expect(result.values.length).to.eql(datasource_multiple.length - 1); + expect(result.values[0].value).to.eql(datasource_multiple[1].value); + expect(result.values[1].value).to.eql(datasource_multiple[2].value); + }); + }); + }); + + describe("date_field_validation", function () { + const pastDate = helper.toISO8601DateOnly(Date.now() - 60000 * 60 * 24 * 3); + const yesterdayDate = helper.toISO8601DateOnly(Date.now() - 60000 * 60 * 24); + const todayDate = helper.toISO8601DateOnly(Date.now()); + const futureDate = helper.toISO8601DateOnly(Date.now() + 60000 * 60 * 24 * 3); + const lastThreeDaysValidation = { + type: "and", + rules: [ + { + type: "greater_than", + equals: false, + value: pastDate, + }, { + type: "less_than", + equals: false, + value: todayDate, + }, + ], + }; + const validMetadata = { + external_id: EXTERNAL_ID_DATE_VALIDATION, + label: EXTERNAL_ID_DATE_VALIDATION, + type: 'date', + default_value: yesterdayDate, + validation: lastThreeDaysValidation, + }; + const invalidMetadata = { + ...validMetadata, + external_id: EXTERNAL_ID_DATE_VALIDATION_2, + default_value: futureDate, + }; + it("should create date field when default value validation passes", function () { + return api.add_metadata_field(validMetadata) + .then((result) => { + expect(result).to.beAMetadataField(); + return api.metadata_field_by_field_id(EXTERNAL_ID_DATE_VALIDATION); }) .then((result) => { expect(result).to.beAMetadataField(); - result.datasource.values.forEach((item) => { - const old_value = datasource_changes.values.find(val => val.external_id === item.external_id).value; - expect(item.value).to.eql(old_value); - }); + expect(result.default_value).to.eql(validMetadata.default_value); + expect(result.validation).to.eql(lastThreeDaysValidation); }); }); + it("should not create date field with illegal default value", function () { + return api.add_metadata_field(invalidMetadata).then(() => { + expect().fail(); + }).catch((res) => { + expect(res.error).not.to.be(void 0); + expect(res.error.message).to.contain("default_value is invalid"); + }); + }); }); - describe("delete_datasource_entries", function () { - it("should delete entries in metadata field datasource", function () { + describe("integer_field_validation", function () { + const validation = { + type: 'less_than', + equals: true, + value: 5, + }; + it("should create integer metadata with valid default value", function () { const metadata = { - external_id: EXTERNAL_ID_DELETE_DATASOURCE_ENTRIES, - label: LABEL_SET_3, - type: "set", - datasource: { - values: [ - { - external_id: "size_1", - value: "big", - }, - { - external_id: "size_2", - value: "small", - }, - ], - }, + external_id: EXTERNAL_ID_INT_VALIDATION, + label: EXTERNAL_ID_INT_VALIDATION, + type: 'integer', + default_value: 5, + validation, }; - const external_ids_for_deletion = [metadata.datasource.values[0].external_id]; return api.add_metadata_field(metadata) - .then(() => api.delete_datasource_entries(EXTERNAL_ID_DELETE_DATASOURCE_ENTRIES, external_ids_for_deletion)) .then((result) => { - expect(result).not.to.be.empty(); - expect(result.values.length).to.eql(1); - expect(result.values[0].external_id).to.eql(metadata.datasource.values[1].external_id); + expect(result).to.beAMetadataField(); + return api.metadata_field_by_field_id(EXTERNAL_ID_INT_VALIDATION); + }).then((result) => { + expect(result).to.beAMetadataField(); + expect(result.validation).to.eql(metadata.validation); + expect(result.default_value).to.eql(metadata.default_value); }); }); - }); - - describe("restore_metadata_field_datasource", function () { - it("should restore a deleted entry in a metadata field datasource", function () { + it("should not create integer metadata with invalid default value", function () { const metadata = { - external_id: EXTERNAL_ID_RESTORE_DATASOURCE_ENTRIES, - label: LABEL_SET_2, - type: "set", - datasource: { - values: [ - { - external_id: "size_1", - value: "big", - }, - { - external_id: "size_2", - value: "small", - }, - ], - }, + external_id: EXTERNAL_ID_INT_VALIDATION_2, + label: EXTERNAL_ID_INT_VALIDATION_2, + type: 'integer', + default_value: 6, + validation, }; - const DELETED_ENTRY = [metadata.datasource.values[0].external_id]; return api.add_metadata_field(metadata) - .then(() => api.delete_datasource_entries(EXTERNAL_ID_RESTORE_DATASOURCE_ENTRIES, DELETED_ENTRY)) - .then((result) => { - expect(result).not.to.be.empty(); - expect(result.values.length).to.eql(1); - expect(result.values[0].external_id).to.eql(metadata.datasource.values[1].external_id); - }) - .then(() => api.restore_metadata_field_datasource(EXTERNAL_ID_RESTORE_DATASOURCE_ENTRIES, DELETED_ENTRY)) .then((result) => { - expect(result).not.to.be.empty(); - expect(result.values.length).to.eql(2); - expect(result.values[0].external_id).to.eql(metadata.datasource.values[0].external_id); - expect(result.values[1].external_id).to.eql(metadata.datasource.values[1].external_id); + expect(result).to.beAMetadataField(); + return api.metadata_field_by_field_id(EXTERNAL_ID_INT_VALIDATION_2); + }).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`); }); }); }); - describe("api.update", function () { - const METADATA_VALUE = "123456"; - it("should update metadata", function () { - return api.update(PUBLIC_ID_UPLOAD, { - metadata: { [EXTERNAL_ID_UPDATE]: METADATA_VALUE }, - }) + describe("restore_metadata_field_datasource", function () { + it("should restore a deleted entry in a metadata field datasource", function () { + return api.delete_datasource_entries(EXTERNAL_ID_SET_3, [DATASOURCE_ENTRY_EXTERNAL_ID]) .then((result) => { - expect(result).not.to.be.empty(); - return api.resource(PUBLIC_ID_UPLOAD); + expect(result).to.beADatasource(); + expect(result.values.length).to.eql(datasource_multiple.length - 1); }) + .then(() => api.restore_metadata_field_datasource(EXTERNAL_ID_SET_3, [DATASOURCE_ENTRY_EXTERNAL_ID])) .then((result) => { - expect(result.metadata[EXTERNAL_ID_UPDATE]).to.eql(METADATA_VALUE); + expect(result).to.beADatasource(); + expect(result.values.length).to.eql(datasource_multiple.length); }); }); }); diff --git a/test/uploader_spec.js b/test/uploader_spec.js index 682b4e49..e7fb32c2 100644 --- a/test/uploader_spec.js +++ b/test/uploader_spec.js @@ -23,6 +23,12 @@ const EMPTY_IMAGE = helper.EMPTY_IMAGE; const RAW_FILE = helper.RAW_FILE; const UPLOAD_TAGS = helper.UPLOAD_TAGS; const uploadImage = helper.uploadImage; +const TEST_ID = Date.now(); + +const METADATA_FIELD_UNIQUE_EXTERNAL_ID = 'metadata_field_external_id_' + TEST_ID; +const METADATA_FIELD_VALUE = 'metadata_field_value_' + TEST_ID; +const METADATA_SAMPLE_DATA = { metadata_color: "red", metadata_shape: "dodecahedron" }; +const METADATA_SAMPLE_DATA_ENCODED = "metadata_color=red|metadata_shape=dodecahedron"; require('jsdom-global')(); @@ -63,6 +69,16 @@ describe("uploader", function () { expect(result.signature).to.eql(expected_signature); }); }); + it("should successfully upload with metadata", function () { + return helper.mockPromise(function (xhr, write, request) { + const metadata_fields = METADATA_SAMPLE_DATA; + uploadImage({ metadata: metadata_fields }); + sinon.assert.calledWith(request, sinon.match({ + method: sinon.match("POST"), + })); + sinon.assert.calledWith(write, sinon.match(helper.uploadParamMatcher("metadata", METADATA_SAMPLE_DATA_ENCODED))); + }); + }); it("should successfully upload url", function () { return cloudinary.v2.uploader.upload("http://cloudinary.com/images/old_logo.png", { tags: UPLOAD_TAGS, @@ -916,6 +932,11 @@ describe("uploader", function () { sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher('quality_analysis', 1))); }); }); + it("should support metadata", function () { + const metadata_fields = METADATA_SAMPLE_DATA; + cloudinary.v2.uploader.explicit("cloudinary", { metadata: metadata_fields }); + sinon.assert.calledWith(spy, sinon.match(helper.uploadParamMatcher("metadata", METADATA_SAMPLE_DATA_ENCODED))); + }); it("should support raw_convert", function () { cloudinary.v2.uploader.explicit("cloudinary", { raw_convert: "google_speech", @@ -963,6 +984,21 @@ describe("uploader", function () { sinon.assert.calledWithMatch(mocked.write, helper.uploadParamMatcher("quality_override", "auto:best")); }); }); + describe("update_metadata", 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.mockPromise(function (xhr, write, request) { + cloudinary.v2.uploader.update_metadata(metadata_fields, public_ids); + sinon.assert.calledWith(request, sinon.match({ + method: sinon.match("POST"), + })); + sinon.assert.calledWith(write, sinon.match(helper.uploadParamMatcher("metadata", "metadata_color=red|metadata_shape="))); + sinon.assert.calledWith(write, sinon.match(helper.uploadParamMatcher("public_ids[]", public_ids[0]))); + sinon.assert.calledWith(write, sinon.match(helper.uploadParamMatcher("public_ids[]", public_ids[1]))); + }); + }); + }); describe("access_control", function () { var acl, acl_string, options, requestSpy, writeSpy; writeSpy = void 0; @@ -1028,4 +1064,77 @@ describe("uploader", function () { sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('timestamp', '1569707219'))); }); }); + describe("structured metadata fields", function () { + const metadata_fields = { [METADATA_FIELD_UNIQUE_EXTERNAL_ID]: METADATA_FIELD_VALUE }; + before(function () { + return cloudinary.v2.api.add_metadata_field({ + 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 () {}); + }); + it("should be set when calling upload with metadata", function () { + return uploadImage({ + tags: UPLOAD_TAGS, + metadata: metadata_fields, + }).then((result) => { + expect(result.metadata[METADATA_FIELD_UNIQUE_EXTERNAL_ID]).to.eql(METADATA_FIELD_VALUE); + }); + }); + it("should be set when calling explicit with metadata", function () { + return uploadImage({ + tags: UPLOAD_TAGS, + }) + .then(result => cloudinary.v2.uploader.explicit(result.public_id, { + type: "upload", + metadata: metadata_fields, + })) + .then((result) => { + expect(result.metadata[METADATA_FIELD_UNIQUE_EXTERNAL_ID]).to.eql(METADATA_FIELD_VALUE); + }); + }); + it("should be updatable with uploader.update_metadata on an existing resource", function () { + let publicId; + return uploadImage({ + tags: UPLOAD_TAGS, + }) + .then((result) => { + publicId = result.public_id; + return cloudinary.v2.uploader.update_metadata(metadata_fields, [publicId]); + }) + .then((result) => { + expect(result).not.to.be.empty(); + expect(result.public_ids.length).to.eql(1); + expect(result.public_ids).to.contain(publicId); + }); + }); + it("should be updatable with uploader.update_metadata on multiple existing resources", 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 cloudinary.v2.uploader.update_metadata(metadata_fields, [resource_1.public_id, resource_2.public_id]); + }) + .then((result) => { + expect(result.public_ids.length).to.eql(2); + expect(result.public_ids).to.contain(resource_1.public_id); + expect(result.public_ids).to.contain(resource_2.public_id); + }); + }); + }); }); From 4c3a6a2f745e2afe7c0c649d9daf595d50ea9f83 Mon Sep 17 00:00:00 2001 From: RTLcoil Date: Tue, 3 Mar 2020 14:52:21 +0200 Subject: [PATCH 2/2] Changed test order in `uploader_spec.js` --- test/uploader_spec.js | 55 ++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/test/uploader_spec.js b/test/uploader_spec.js index e7fb32c2..77047d63 100644 --- a/test/uploader_spec.js +++ b/test/uploader_spec.js @@ -1037,33 +1037,6 @@ describe("uploader", function () { }); }); - describe("sign requests", function () { - var configBck2 = void 0; - var writeSpy; - writeSpy = void 0; - beforeEach(function () { - writeSpy = sinon.spy(ClientRequest.prototype, 'write'); - configBck2 = cloudinary.config(); - cloudinary.config({ - api_key: "1234", - api_secret: "", - }); - }); - afterEach(function () { - cloudinary.config(configBck2); - writeSpy.restore(); - }); - it("should allow a signature and timestamp parameter on uploads", function () { - cloudinary.v2.uploader.upload(IMAGE_FILE, { - public_id: 'folder/file', - version: '1234', - timestamp: 1569707219, - signature: 'b77fc0b0dffbf7e74bdad36b615225fb6daff81e', - }); - sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('signature', "b77fc0b0dffbf7e74bdad36b615225fb6daff81e"))); - sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('timestamp', '1569707219'))); - }); - }); describe("structured metadata fields", function () { const metadata_fields = { [METADATA_FIELD_UNIQUE_EXTERNAL_ID]: METADATA_FIELD_VALUE }; before(function () { @@ -1137,4 +1110,32 @@ describe("uploader", function () { }); }); }); + + describe("sign requests", function () { + var configBck2 = void 0; + var writeSpy; + writeSpy = void 0; + beforeEach(function () { + writeSpy = sinon.spy(ClientRequest.prototype, 'write'); + configBck2 = cloudinary.config(); + cloudinary.config({ + api_key: "1234", + api_secret: "", + }); + }); + afterEach(function () { + cloudinary.config(configBck2); + writeSpy.restore(); + }); + it("should allow a signature and timestamp parameter on uploads", function () { + cloudinary.v2.uploader.upload(IMAGE_FILE, { + public_id: 'folder/file', + version: '1234', + timestamp: 1569707219, + signature: 'b77fc0b0dffbf7e74bdad36b615225fb6daff81e', + }); + sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('signature', "b77fc0b0dffbf7e74bdad36b615225fb6daff81e"))); + sinon.assert.calledWith(writeSpy, sinon.match(helper.uploadParamMatcher('timestamp', '1569707219'))); + }); + }); });