From 0ea5beadeec02edb0bd7726fc2676875066f1e7c Mon Sep 17 00:00:00 2001 From: Philip Ellis Date: Fri, 19 Jan 2024 15:53:15 -0600 Subject: [PATCH] added all checks --- postman-script/partialUpdate/.gitignore | 2 - .../partialUpdate/DeployIncremental.js | 507 ------------------ .../partialUpdate/PostmanCovertions.js | 143 ----- postman-script/partialUpdate/Utils.js | 37 -- postman-script/partialUpdate/index.js | 11 - .../partialUpdate/package-lock.json | 131 ----- postman-script/partialUpdate/package.json | 16 - postman-script/partialUpdate/postmanAPI.js | 236 -------- .../updateByFolder/PostmanCovertions.js | 37 +- postman-script/updateByFolder/index.js | 117 +++- postman-script/updateByFolder/postmanAPI.js | 15 +- 11 files changed, 148 insertions(+), 1104 deletions(-) delete mode 100644 postman-script/partialUpdate/.gitignore delete mode 100644 postman-script/partialUpdate/DeployIncremental.js delete mode 100644 postman-script/partialUpdate/PostmanCovertions.js delete mode 100644 postman-script/partialUpdate/Utils.js delete mode 100644 postman-script/partialUpdate/index.js delete mode 100644 postman-script/partialUpdate/package-lock.json delete mode 100644 postman-script/partialUpdate/package.json delete mode 100644 postman-script/partialUpdate/postmanAPI.js diff --git a/postman-script/partialUpdate/.gitignore b/postman-script/partialUpdate/.gitignore deleted file mode 100644 index 6ed48a9..0000000 --- a/postman-script/partialUpdate/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.env -node_modules diff --git a/postman-script/partialUpdate/DeployIncremental.js b/postman-script/partialUpdate/DeployIncremental.js deleted file mode 100644 index 8a827cd..0000000 --- a/postman-script/partialUpdate/DeployIncremental.js +++ /dev/null @@ -1,507 +0,0 @@ -// Deploy collection inncrementally -// --------------------------------------------------------------- -// Cycle through the collection objects and deploy them one by one -// --------------------------------------------------------------- -// Folders -> Requests -> Responses -// --------------------------------------------------------------- -// An updated object has a different id. -// If the id matches, then the object is unchanged. -// If the id does not match, then the object is new. -// In the end delete all objects that are not in the updated collection. -// Sort the objects and update the parent order property. -// --------------------------------------------------------------- -// Except for folders which would force the update of the entire collection. -// --------------------------------------------------------------- - -const pmConvert = require('./PostmanCovertions') -const pmAPI = require('./postmanAPI') -var crypto = require('crypto'); -const { GenID } = require('./Utils') - -const deployIncremental = async (privateRemoteCollectionId, localCollection, publicRemoteCollectionId) => { - let remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId) - - console.log('Incremental deployment of collection ', localCollection.info.name) - - // const collectioHeadHasChanged = - await upadteCollectionHead(remoteCollection, localCollection) - - remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId) - - // const foldersHaveChanged = - await mergeFolders(remoteCollection, localCollection) - - remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId) - - // const requestsHaveChanged = - await mergeRequests(remoteCollection, localCollection) - - remoteCollection = await refreshRemoteCollection(privateRemoteCollectionId) - - // const responsesHaveChanged = - await mergeResponses(remoteCollection, localCollection) - - // should we always merge into the public collection? - // There is teh case that if an error happens in the merge phase - // the private collection is fully updated - // and in the next run the public collection will NOT be updated - // because there are no changes in the private collection - - // if (!(collectioHeadHasChanged || foldersHaveChanged || requestsHaveChanged || responsesHaveChanged)) { - // console.log('Incremental deployment of collection ', localCollection.info.name, ' completed\n\n') - // return - // } - const msg = 'Merging to public collection' - console.log('\n' + msg + '...') - await new pmAPI.Collection(privateRemoteCollectionId).merge(publicRemoteCollectionId) - .then(() => { console.log(msg, '-> OK\n') }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - console.log('Incremental deployment of collection ', localCollection.info.name, ' completed\n\n') -} - -async function upadteCollectionHead (remoteCollection, localCollection) { - // the colelction head shoul dbe updated if there are changes in - // Authorization - // Pre-request Script - // Tests - // Variables - - const localEmptyCollection = { ...localCollection } - localEmptyCollection.item = [] - - // Check changes in info - const hasChangesInfo = checkInfoChanges(remoteCollection, localCollection) - - // Check if there are changes in the Authorization - const hasChangesAuth = checkObjectChanges(remoteCollection.collection.auth, localEmptyCollection.auth) - - // Check if there are changes in the Scripts (pre-request and tests) - const hasChangesPreRequestScript = checkScriptChanges('prerequest', remoteCollection, localEmptyCollection) - - const hasChangesTestScript = checkScriptChanges('test', remoteCollection, localEmptyCollection) - - // Check if there are changes in the Variables - const hasChangesVariables = checkVariableChanges(remoteCollection, localEmptyCollection) - - const hasFolderSortChanges = checkFolderSortChanges(remoteCollection, localCollection) - - const hasChanges = ( - hasFolderSortChanges || - hasChangesInfo || - hasChangesAuth || - hasChangesPreRequestScript || - hasChangesTestScript || - hasChangesVariables - ) - - if (hasChanges) { - const msg = 'Updating collection head' - console.log('\n' + msg + '...') - await new pmAPI.Collection(remoteCollection.collection.info.uid) - .update({ collection: localEmptyCollection }) - .then(() => { console.log(msg, '-> OK\n') }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - return hasChanges -} - -const checkFolderSortChanges = (remoteCollection, localCollection) => { - const remoteFolders = remoteCollection.collection.item - .map(folder => ({ id: folder.id })) - const localFolders = localCollection.item - .filter(folder => !folder.folder) - .map(folder => ({ id: folder.id })) - - const remoteFoldersHash = GenID(JSON.stringify(remoteFolders)) - const localFoldersHash = GenID(JSON.stringify(localFolders)) - - return remoteFoldersHash !== localFoldersHash -} - -const checkInfoChanges = (remoteCollection, localEmptyCollection) => { - // collection info does not have a specific id - // so we need to generate a hash and compare them - // The hash is only beig generated for name, description and schema - - const { name, description, schema } = remoteCollection.collection.info - const remoteInfo = { name, description, schema } - - const { name: localName, description: localDescription, schema: localSchema } = localEmptyCollection.info - const localInfo = { name: localName, description: localDescription, schema: localSchema } - - const remoteInfoHash = calculateHash(JSON.stringify(remoteInfo)) - const localInfoHash = calculateHash(JSON.stringify(localInfo)) - - return remoteInfoHash !== localInfoHash -} - -const checkObjectChanges = (remoteCollectionObject, localCollectionObject) => { - if (!remoteCollectionObject && !localCollectionObject) { - return false - } - if (!remoteCollectionObject || !localCollectionObject) { - return true - } - - // certain object like auth do not have an id, - // so we need to generate on and compare them - const remoteCollectionAuthID = GenID(JSON.stringify(remoteCollectionObject)) - const localCollectionAuthID = GenID(JSON.stringify(localCollectionObject)) - return remoteCollectionAuthID !== localCollectionAuthID -} - -const checkScriptChanges = (scriptType, remoteCollection, localCollection) => { - // RB 2020-10-20: The collection may be empty or have no events at all - let remoteScript = null - let localScript = null - if (remoteCollection.collection.event) { - remoteScript = remoteCollection.collection.event.find(event => event.listen === scriptType) - } - - if (localCollection.event) { - localScript = localCollection.event.find(event => event.listen === scriptType) - } - - // const remoteScript = remoteCollection.collection.event.find(event => event.listen === scriptType) - // const localScript = localCollection.event.find(event => event.listen === scriptType) - - if (!remoteScript && !localScript) { - return false - } - if (!remoteScript || !localScript) { - return true - } - // files can be big, so we hash them - const remoteHash = calculateHash(remoteScript.script.exec[0]) - const localHash = calculateHash(localScript.script.exec[0]) - - return remoteHash !== localHash -} - -const checkVariableChanges = (remoteCollection, localCollection) => { - const remoteVariables = remoteCollection.collection.variable - const localVariables = localCollection.variable.map(variable => ({ key: variable.key, value: variable.value })) - - // check if null - if (!remoteVariables && !localVariables) { - return false - } - if (!remoteVariables || !localVariables) { - return true - } - - // although the local collection does have a deterministic id - // the remote variable looses that value when it is updated - // so we need to generate an id for the remote variable - - const remoteVariablesHash = GenID(remoteVariables) - const localVariablesHash = GenID(localVariables) - - return remoteVariablesHash !== localVariablesHash -} - -async function mergeFolders (remoteCollection, localCollection) { - console.log(' Deploying Folders:') - - const remoteFolders = getAllFoldersFromCollectionItem(remoteCollection.collection.item) - const localFolders = localCollection.item // all folders - - const newFolders = localFolders.filter(localFolder => !remoteFolders.find(remoteFolder => remoteFolder.id === localFolder.id)) - const oldFolders = remoteFolders.filter(remoteFolder => !localFolders.find(localFolder => localFolder.id === remoteFolder.id)) - - let hasChanges = newFolders.length > 0 || oldFolders.length > 0 - - if (!hasChanges) { - console.log(' -> No changes') - return hasChanges - } - - // create new folders - for (const folder of newFolders) { - const msg = ` Creating new folder [${folder.name}]` - await new pmAPI.Folder(remoteCollection.collection.info.uid) - .create(folder) - .then(() => { - hasChanges = true - console.log(msg, '-> OK') - }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - - // delete old folders - for (const folder of oldFolders) { - const msg = ` Deleting old folder [${folder.name}]` - await new pmAPI.Folder(remoteCollection.collection.info.uid) - .delete(folder.id) - .then(() => { - hasChanges = true - console.log(msg, '-> OK') - }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - - // sort folders is not supported for now - // const order = localFolders.map(folder => folder.id) - // const msg = ' Sorting folders' - - // // create a temporsary root folder - // const rootFolder = await new pmAPI.Folder(remoteCollection.collection.info.uid) - // .create({ id: GenID(), name: 'root', folders: order }) - // .catch((error) => { - // console.log(msg, '-> FAIL') - // handlePostmanAPIError(error) - // }) - // console.log('root folder', rootFolder) - // // move all remote folders into root folder - - return hasChanges -} - -async function mergeRequests (remoteCollection, localCollection) { - const remoteFolders = getAllFoldersFromCollectionItem(remoteCollection.collection.item) - const localFolders = localCollection.item // all folders - - console.log('\n Deploying Requests:') - let anyRequestHasChanged = false - - // loop folders - for (const localFolder of localFolders) { - const remoteRemoteFolder = remoteFolders.find(remoteFolder => ((remoteFolder.id === localFolder.id))) - - // TODO: RB: get requests by folder - // handle undefined items - remoteRemoteFolder.item = remoteRemoteFolder.item || [] - - // filter out anything that is not a request - remoteRemoteFolder.item = remoteRemoteFolder.item.filter(request => request.request) - - const remoteRequests = remoteRemoteFolder.item - const localRequests = localFolder.item - - // Identify old and new requests - const newRequests = localRequests.filter(localRequest => !remoteRequests.find(remoteRequest => remoteRequest.id === localRequest.id)) - const oldRequests = remoteRequests.filter(remoteRequest => !localRequests.find(localRequest => localRequest.id === remoteRequest.id)) - - const requestsInFolderHaveChanges = newRequests.length > 0 || oldRequests.length > 0 - - if (!requestsInFolderHaveChanges) { - console.log(' In Folder: ', localFolder.name, '-> No changes') - continue - } - console.log(' In Folder: ', localFolder.name) - - // create new requests - for (const request of newRequests) { - // check request format and convert if necessary - let pmRequest = null - if (!request.request) { // => Postman Format - pmRequest = request - } else { // => OpenAPI Format - pmRequest = pmConvert.requestFromLocal(request) - } - const msg = ` Creating new request [${request.name}]` - - await new pmAPI.Request(remoteCollection.collection.info.uid) - .create(pmRequest, localFolder.id) - .then((req) => { - console.log(msg, '-> OK') - return req - }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - // console.log('\nequest', request) - // console.log('\npmRequest', pmRequest) - // console.log('\nremoteRequest', remoteRequest) - } - - // delete old requests - for (const request of oldRequests) { - const msg = ` Deleting old request [${request.name}]` - await new pmAPI.Request(remoteCollection.collection.info.uid) - .delete(request.id) - .then(() => { - console.log(msg, '-> OK') - }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - - if (requestsInFolderHaveChanges) { - // sort requests in folder - const order = localRequests.map(request => request.id) - const msg = ` Sorting requests in folder [${localFolder.name}]` - await new pmAPI.Folder(remoteCollection.collection.info.uid) - .update(localFolder.id, { order }) - .then(() => { console.log(msg, '-> OK') }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - anyRequestHasChanged = anyRequestHasChanged || requestsInFolderHaveChanges - } - return anyRequestHasChanged -} - -async function mergeResponses (remoteCollection, localCollection) { - console.log('\n Deploying Response:') - const remoteFolders = getAllFoldersFromCollectionItem(remoteCollection.collection.item) - const localFolders = localCollection.item - - let anyResponseHasChanged = false - // loop folders - for (const localFolder of localFolders) { - const remoteRemoteFolder = remoteFolders.find(remoteFolder => ((remoteFolder.id === localFolder.id))) - - // handle undefined items - remoteRemoteFolder.item = remoteRemoteFolder.item || [] - - // filter out anything that is not a request - remoteRemoteFolder.item = remoteRemoteFolder.item.filter(request => request.request) - - const remoteRequests = remoteRemoteFolder.item - const localRequests = localFolder.item - - console.log(' In Folder: ', localFolder.name) - // loop requests - for (const localRequest of localRequests) { - // Postman Request format does not have a response property - const remoteResponses = remoteRequests.find(remoteRequest => remoteRequest.id === localRequest.id).response - const localResponses = localRequest.response - // the request may not have responses - if (!localResponses) { - continue - } - - const newResponses = localResponses.filter(localResponse => !remoteResponses.find(remoteResponse => remoteResponse.id === localResponse.id)) - const oldResponses = remoteResponses.filter(remoteResponse => !localResponses.find(localResponse => localResponse.id === remoteResponse.id)) - - const ResponsesInReqquestHaveChanges = newResponses.length > 0 || oldResponses.length > 0 - if (!ResponsesInReqquestHaveChanges) { - console.log(' In Request: ', localRequest.name, '-> No changes') - continue - } - console.log(' In Request: ', localRequest.name) - - // create new responses - for (const response of newResponses) { - const pmResponse = pmConvert.responseFromLocal(response) - const msg = ` Creating new response [${response.code} ${response.status}]` - await new pmAPI.Response(remoteCollection.collection.info.uid) - .create(pmResponse, localRequest.id) - .then(() => { - console.log(msg, '-> OK') - }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - - // delete old responses - for (const response of oldResponses) { - const msg = ` Deleting old response [${response.code} ${response.status}]` - await new pmAPI.Response(remoteCollection.collection.info.uid) - .delete(response.id) - .then(() => { - console.log(msg, '-> OK') - }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - - // updating the requests with the order of the responses, doesn't seem to be necessary - if (ResponsesInReqquestHaveChanges) { - // sort responses in requests - const responsesOrder = localResponses.map(response => response.id) - const msg = ` Sorting responses in request [${localRequest.name}]` - await new pmAPI.Request(remoteCollection.collection.info._postman_id) - .update(localRequest.id, - { - responses_order: responsesOrder - }) - .then(() => { console.log(msg, '-> OK') }) - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - } - anyResponseHasChanged = anyResponseHasChanged || ResponsesInReqquestHaveChanges - } - } - return anyResponseHasChanged -} - -async function refreshRemoteCollection (remoteCollectionID) { - const msg = 'Refreshing remote collection' - console.log('\n' + msg + '...\n') - const remoteCollection = await new pmAPI.Collection(remoteCollectionID).get() - .catch((error) => { - console.log(msg, '-> FAIL') - handlePostmanAPIError(error) - }) - return remoteCollection -} - -// return all folders in the collection -// independently of where they are -const getAllFoldersFromCollectionItem = (collectionItem) => { - const folders = [] - const processItem = (item) => { - if (!item.request && !item.responses) { - folders.push({ id: item.id, name: item.name, item: item.item }) - } - if (item.item) { - item.item.forEach(processItem) - } - } - collectionItem.forEach(processItem) - return folders -} - -// Handle axios error -const handlePostmanAPIError = (error) => { - if (error.response) { - // The request was made and the server responded with a status code - // that falls out of the range of 2xx - console.log('API ERROR:', error.response.data) - } else { - // The request was made but no response was received - // `error.request` is an instance of XMLHttpRequest in the browser and an instance of - // http.ClientRequest in node.js - console.log('NO RESPONSE:', error.message) - if (error.cause) { - console.log('CAUSE:', error.cause) - } - } - const { method, url, data } = error.config - const smallData = data.substring(0, 1000) - console.log('REQUEST DETAILS', { method, url, smallData }) - process.exit(1) -} - -const calculateHash = (stringToHash) => { - return crypto.createHash('sha256').update(stringToHash).digest('hex') -} - -module.exports = { - deployIncremental -} \ No newline at end of file diff --git a/postman-script/partialUpdate/PostmanCovertions.js b/postman-script/partialUpdate/PostmanCovertions.js deleted file mode 100644 index 417ff4d..0000000 --- a/postman-script/partialUpdate/PostmanCovertions.js +++ /dev/null @@ -1,143 +0,0 @@ -// Postman Convertions -// ------------------------------------------------------------ -// Handles the conversions from the local generated object -// to the postman api specific objects. -// The converted objects are necessary for the updates to work. -// ------------------------------------------------------------ -// The interesting part is that the postman api uses a different -// object structure for folders, requests, adn responses, -// when compared to what you get from the collection json file. -// ------------------------------------------------------------ - -const requestFromLocal = (localRequest) => { - // console.log('localRequest', localRequest) - let url = localRequest.request.url.protocol + '://' + localRequest.request.url.host + localRequest.request.url.path - - let data = [] - let dataMode = null - let rawModeData = null - if (localRequest.request.body && localRequest.request.body.urlencoded) { - data = dataFromLocalURLEncode(localRequest.request.body.urlencoded) - dataMode = localRequest.request.body.mode - rawModeData = localRequest.request.body.raw - } - - let queryParams = [] - if (localRequest.request.url.query) { - queryParams = dataFromLocalURLEncode(localRequest.request.url.query) - url += '?' - for (const param of queryParams) { - if (param.enabled === false) continue - url += param.key + '=' + param.value + '&' - } - url = url.slice(0, -1) - } - - let pathVariableData = [] - if (localRequest.request.url.variable) { - pathVariableData = dataFromLocalURLEncode(localRequest.request.url.variable) - } - - let headerData = [] - if (localRequest.request.header) { - headerData = dataFromLocalURLEncode(localRequest.request.header) - .map((header) => ({ key: header.key, value: header.value, enabled: header.enabled, description: header.description })) - } - - const request = { - // owner: '8119550', - // lastUpdatedBy: '8119550', - // lastRevision: 32526900683, - // folder: 'dfa47710-b3d3-4a2c-bbc8-fbd25ad12244', - // collection: 'fa89c950-c947-4061-9d13-fb18d7c6bc51', - - id: localRequest.id, // - name: localRequest.name, // - - dataMode, // - data, // - auth: localRequest.request.auth, // - events: localRequest.events, // - // - rawModeData, // body os request - // - descriptionFormat: localRequest.descriptionFormat, // it can be in either ``html`` or ``markdown`` formats. - description: localRequest.request.description, // - // Headers - headers: null, - headerData, - // - variables: localRequest.variables, - method: localRequest.request.method, // - - // Path Variables - pathVariables: pathVariableData, // - pathVariableData, // - // - url, // - preRequestScript: localRequest.preRequestScript, - tests: localRequest.tests, - currentHelper: localRequest.currentHelper, - helperAttributes: localRequest.helperAttributes, - queryParams, // - - protocolProfileBehavior: localRequest.protocolProfileBehavior, - dataDisabled: localRequest.dataDisabled, - responses_order: localRequest.responses_order - - // createdAt: '2023-09-12T16:25:20.000Z', - // updatedAt: '2023-09-12T16:25:23.000Z', - // dataOptions: {{"raw":{}}}, - } - return request - } - - const responseFromLocal = (localResponse) => { - const headers = localResponse.header.map((item) => ({ key: item.key, value: item.value })) - const response = { - // owner: '8119550', - // lastUpdatedBy: '8119550', - // lastRevision: 32546597265, - // request: '331bbc94-5425-46f3-8c02-31c353d2ced8', - - id: localResponse.id, // - name: localResponse.name, // - status: localResponse.status, // - responseCode: { - code: localResponse.code, // - name: localResponse.status, // - detail: '' - }, - // time: null, - headers, // - cookies: [], - mime: null, - text: localResponse.body, // - language: 'Text', // - rawDataType: 'text'// - // requestObject: null, - // createdAt: '2023-09-13T14:53:05.000Z', - // updatedAt: '2023-09-13T14:53:05.000Z' - } - - return response - } - - const dataFromLocalURLEncode = (localFormData) => { - const data = [] - for (const param of localFormData) { - const item = { - key: param.key, - description: param.description, - value: param.value, - enabled: !param.disabled - } - data.push(item) - } - return data - } - - module.exports = { - requestFromLocal, - responseFromLocal - } \ No newline at end of file diff --git a/postman-script/partialUpdate/Utils.js b/postman-script/partialUpdate/Utils.js deleted file mode 100644 index 1a2fedc..0000000 --- a/postman-script/partialUpdate/Utils.js +++ /dev/null @@ -1,37 +0,0 @@ -const uuid = require('uuid') -const NAMESPACE = '33c4e6fc-44cb-4190-b19f-4a02821bc8c3' - -const genID = (objectJSON = null) => { - if (objectJSON) { - return uuid.v5(objectJSON, NAMESPACE) - } else { - return uuid.v4() - } -} - -// Sort 2 objects by name -const byName = (a, b) => { - if (a.name < b.name) { - return -1 - } else if (a.name > b.name) { - return 1 - } - return 0 -} - -// Sort two object by priority -const byPriority = (a, b) => { - if (a['x-box-priority']) { - return -1 - } else if (b['x-box-priority']) { - return 1 - } - return 0 -} - -module.exports = { - GenID: genID, - ByName: byName, - ByPriority: byPriority - // logAxiosError -} \ No newline at end of file diff --git a/postman-script/partialUpdate/index.js b/postman-script/partialUpdate/index.js deleted file mode 100644 index f4e247e..0000000 --- a/postman-script/partialUpdate/index.js +++ /dev/null @@ -1,11 +0,0 @@ -const fs = require('fs') -const { deployIncremental } = require('./DeployIncremental') - -const release = async () => { - let privateRemoteCollectionId = '23836355-c5640083-7523-4ad7-9f92-b23e079cbb7b' - let publicRemoteCollectionId = '23836355-6224d51a-d924-4c39-a58f-6970735aac8e' - let localCollection = JSON.parse(fs.readFileSync(`C:\\git\\api-specs\\postman\\collections\\sailpoint-api-v3.json`).toString()) - await deployIncremental(privateRemoteCollectionId, localCollection, publicRemoteCollectionId) -} - -release() \ No newline at end of file diff --git a/postman-script/partialUpdate/package-lock.json b/postman-script/partialUpdate/package-lock.json deleted file mode 100644 index 9764f94..0000000 --- a/postman-script/partialUpdate/package-lock.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "name": "partialupdate", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "partialupdate", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "axios": "^1.6.5", - "dotenv": "^16.3.1", - "uuid": "^9.0.1" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - } - } -} diff --git a/postman-script/partialUpdate/package.json b/postman-script/partialUpdate/package.json deleted file mode 100644 index dbd5f7c..0000000 --- a/postman-script/partialUpdate/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "partialupdate", - "version": "1.0.0", - "description": "partial updates to postman files", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "axios": "^1.6.5", - "dotenv": "^16.3.1", - "uuid": "^9.0.1" - } -} diff --git a/postman-script/partialUpdate/postmanAPI.js b/postman-script/partialUpdate/postmanAPI.js deleted file mode 100644 index 0cbe5ea..0000000 --- a/postman-script/partialUpdate/postmanAPI.js +++ /dev/null @@ -1,236 +0,0 @@ -require('dotenv').config() -const axios = require('axios') - -class Collection { - constructor (collectionId) { - this.collectionId = collectionId - this.apiKey = process.env.POSTMAN_API_KEY - this.axios = axios.create({ - timeout: 1000 * 60, // 60 seconds - headers: { 'Content-Type': 'application/json', 'X-Api-Key': this.apiKey } - }) - } - - async get () { - return await this.axios.get( - `https://api.getpostman.com/collections/${this.collectionId}` - , { timeout: 1000 * 60 * 5 } // 5 minutes - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error getting collection ${this.collectionId}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async merge (destinationCollectionId, strategy = 'updateSourceWithDestination') { - return await this.axios.post( - 'https://api.getpostman.com/collections/merge', - { source: this.collectionId, destination: destinationCollectionId, strategy }, - { timeout: 1000 * 60 * 5 } // 5 minutes - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error merging collection from ${this.collectionId} to ${destinationCollectionId}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async update (collection) { - return await this.axios.put( - `https://api.getpostman.com/collections/${this.collectionId}`, - collection, - { timeout: 1000 * 60 * 5 } // 5 minutes - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error updating collection ${collection.id}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } -} - -class Folder { - constructor (collectionId) { - this.collectionId = collectionId - this.apiKey = process.env.POSTMAN_API_KEY - this.axios = axios.create({ - timeout: 1000 * 60, // 60 seconds - headers: { 'Content-Type': 'application/json', 'X-Api-Key': this.apiKey } - }) - } - - async get (folderId) { - return await this.axios.get( - `https://api.getpostman.com/collections/${this.collectionId}/folders/${folderId}` - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error getting folder ${folderId}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async create (folder) { - return await this.axios.post( - `https://api.getpostman.com/collections/${this.collectionId}/folders`, - folder - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error creating folder ${folder.Id}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async update (folderId, folder) { - return await this.axios.put( - `https://api.getpostman.com/collections/${this.collectionId}/folders/${folderId}`, - folder - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error updating folder ${folder.Id}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async delete (folderId) { - return await this.axios.delete( - `https://api.getpostman.com/collections/${this.collectionId}/folders/${folderId}` - - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error deleting folder ${folderId}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } -} // class Folder - -class Request { - constructor (collectionId) { - this.collectionId = collectionId - this.apiKey = process.env.POSTMAN_API_KEY - this.axios = axios.create({ - timeout: 1000 * 60, // 60 seconds - headers: { 'Content-Type': 'application/json', 'X-Api-Key': this.apiKey } - }) - } - - async get (requestId) { - return await this.axios.get( - `https://api.getpostman.com/collections/${this.collectionId}/requests/${requestId}` - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error getting request ${requestId}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async create (request, folderId) { - return await this.axios.post( - `https://api.getpostman.com/collections/${this.collectionId}/requests`, - request, - { params: { folder: folderId } } - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error creating request ${request.id}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async update (requestId, request) { - return await this.axios.put( - `https://api.getpostman.com/collections/${this.collectionId}/requests/${requestId}`, - request - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error updating request ${request.id}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } - - async delete (requestId) { - return await this.axios.delete( - `https://api.getpostman.com/collections/${this.collectionId}/requests/${requestId}` - - ).then(function (response) { - if (response.status !== 200) { - throw new Error(`Error deleting request ${requestId}: ${response.status} ${response.statusText}`) - } else { - return response.data - } - }) - } -} // class Request - -class Response { - constructor (collectionId) { - this.collectionId = collectionId - this.apiKey = process.env.POSTMAN_API_KEY - this.axios = axios.create({ - timeout: 1000 * 60, // 60 seconds - headers: { 'Content-Type': 'application/json', 'X-Api-Key': this.apiKey } - }) - } - - async get (responseId) { - return await this.axios.get( - `https://api.getpostman.com/collections/${this.collectionId}/responses/${responseId}` - ).then(function (axiosResp) { - if (axiosResp.status !== 200) { - throw new Error(`Error getting response ${responseId}: ${axiosResp.status} ${axiosResp.statusText}`) - } else { - return axiosResp.data - } - }) - } - - async create (response, requestId) { - return await this.axios.post( - `https://api.getpostman.com/collections/${this.collectionId}/responses`, - response, - { params: { request: requestId } } - ).then(function (axiosResp) { - if (axiosResp.status !== 200) { - throw new Error(`Error creating response ${response.id}: ${axiosResp.status} ${axiosResp.statusText}`) - } else { - return axiosResp.data - } - }) - } - - async delete (responseId) { - return await this.axios.delete( - `https://api.getpostman.com/collections/${this.collectionId}/responses/${responseId}` - - ).then(function (axiosResp) { - if (axiosResp.status !== 200) { - throw new Error(`Error deleting response ${responseId}: ${axiosResp.status} ${axiosResp.statusText}`) - } else { - return axiosResp.data - } - }) - } -} // class Response - -module.exports = { - Collection, - Folder, - Request, - Response -} \ No newline at end of file diff --git a/postman-script/updateByFolder/PostmanCovertions.js b/postman-script/updateByFolder/PostmanCovertions.js index 41fbf84..50290b7 100644 --- a/postman-script/updateByFolder/PostmanCovertions.js +++ b/postman-script/updateByFolder/PostmanCovertions.js @@ -12,7 +12,7 @@ const requestFromLocal = (localRequest, responses) => { // console.log('localRequest', localRequest) - let url = localRequest.request.url.host + '/' + localRequest.request.url.path + let url = localRequest.request.url.host + '/' + localRequest.request.url.path.join('/') let data = [] let dataMode = null @@ -48,6 +48,12 @@ const requestFromLocal = (localRequest, responses) => { .map((header) => ({ key: header.key, value: header.value, enabled: header.enabled, description: header.description })) } + let headers = [] + if (localRequest.request.header) { + headers = dataFromLocalURLEncode(localRequest.request.header) + .map((header) => ({ key: header.key, value: header.value, description: header.description })) + } + if (JSON.stringify(localRequest.request.description) === '{}') { localRequest.request.description = '' } @@ -70,9 +76,9 @@ const requestFromLocal = (localRequest, responses) => { rawModeData, // body os request // descriptionFormat: localRequest.descriptionFormat, // it can be in either ``html`` or ``markdown`` formats. - description: localRequest.request.description.content ? localRequest.request.description.content : localRequest.request.description, // + description: localRequest.request.description && localRequest.request.description.content ? localRequest.request.description.content : localRequest.request.description, // // Headers - headers: localRequest.request.header, + headers: headers, headerData, // variables: localRequest.variables, @@ -102,7 +108,17 @@ const requestFromLocal = (localRequest, responses) => { } const responseFromLocal = (localResponse, requestObject) => { - const headers = localResponse.header.map((item) => ({ key: item.key, value: item.value })) + const headers = localResponse.header + .map((item) => ({ key: item.key, value: item.value })) + .sort((a, b) => { + if (a.key < b.key) { + return -1; + } + if (a.key > b.key) { + return 1; + } + return 0; + }); const response = { // owner: '8119550', // lastUpdatedBy: '8119550', @@ -121,7 +137,7 @@ const requestFromLocal = (localRequest, responses) => { headers, // cookies: [], mime: null, - text: localResponse.body, // + text: localResponse.body ? localResponse.body : '', // language: 'json', // rawDataType: 'text',// requestObject: requestObject, @@ -135,11 +151,18 @@ const requestFromLocal = (localRequest, responses) => { const dataFromLocalURLEncode = (localFormData) => { const data = [] for (const param of localFormData) { + // check if param.key is a number + if (!param.key || !isNaN(param.key)) { + continue + } + if (JSON.stringify(param.description) === '{}') { + param.description = '' + } const item = { key: param.key, - description: param.description, + description: param.description && param.description.content ? param.description.content : param.description, value: param.value, - enabled: !param.disabled + enabled: false } data.push(item) } diff --git a/postman-script/updateByFolder/index.js b/postman-script/updateByFolder/index.js index 8fa353e..e236c2a 100644 --- a/postman-script/updateByFolder/index.js +++ b/postman-script/updateByFolder/index.js @@ -14,7 +14,7 @@ const release = async () => { - // This function just cleans up the variables so they match what is returned in postman + // This step just cleans up the variables so they match what is returned in postman for (let variable of localCollection.variable) { if (variable.type) { delete variable.type @@ -41,11 +41,35 @@ const release = async () => { // add any missing folders for (let item of localCollection.item) { let folder = getMatchingFolder(item, remoteCollection.collection.item) + if (checkIfDifferent(folder.description, item.description)) { + console.log(`updating folder ${folder.name}`) + await new pmAPI.Folder(publicRemoteCollectionId).update(folder.id, { description: item.description }) + console.log(`updated folder ${folder.name}`) + } if (folder == null) { await updateEntireFolder(item) } } + // delete any folders that are no longer in the collection + for (let folder of remoteCollection.collection.item) { + let localFolder = getMatchingFolder(folder, localCollection.item) + if (localFolder == null) { + await new pmAPI.Folder(publicRemoteCollectionId).delete(folder.id) + } + } + + // delete any requests that are no longer in the collection + for (let folder of remoteCollection.collection.item) { + let localFolder = getMatchingFolder(folder, localCollection.item) + for (let items of folder.item) { + let remoteRequest = getMatchingRequest(items, localFolder.item) + if (remoteRequest == null) { + await new pmAPI.Request(publicRemoteCollectionId).delete(items.id) + } + } + } + // update any requests that have changed for (let item of localCollection.item) { let folder = getMatchingFolder(item, remoteCollection.collection.item) @@ -54,7 +78,6 @@ const release = async () => { } } - console.log(remoteCollection) } @@ -69,7 +92,7 @@ function getMatchingFolder(localFolder, remoteFolders) { function getMatchingRequest(localRequest, remoteRequests) { for (let request of remoteRequests) { - if (request.name === localRequest.name) { + if (request.name === localRequest.name && request.request.method === localRequest.request.method && localRequest.request.url.host + '/' + localRequest.request.url.path.join('/') === request.request.url.host + '/' + request.request.url.path.join('/')) { return request } } @@ -77,6 +100,9 @@ function getMatchingRequest(localRequest, remoteRequests) { } function buildRequestBody(items) { + if (items === null) { + return null + } let responses = [] for (let response of items.response) { @@ -87,12 +113,87 @@ function buildRequestBody(items) { } function checkIfDifferent(source, dest) { - if (JSON.stringify(source) === JSON.stringify(dest)) { + removeIdFields(source) + removeIdFields(dest) + if (isDeepEqual(source, dest)) { return false } return true } +function removeIdFields(obj) { + if (typeof obj !== 'object') { + return + } + if (obj === null) { + return + } + // Check if the current object has the 'id' property + if (obj.hasOwnProperty('id')) { + delete obj.id; + } + + // Recursively call removeIdFields on each property if it's an object + for (let key in obj) { + if (typeof obj[key] === 'object' && obj[key] !== null) { + removeIdFields(obj[key]); + } + } +} + +function isNullorEmpty(obj) { + if (obj === null || obj === '' || obj === undefined) { + return true + } + return false + +} + +function isDeepEqual(obj1, obj2) { + if (areValuesEqual(obj1, obj2)) { + return true + } + + if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 == null || obj2 == null) { + return false; + } + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) { + return false; + } + + for (let key of keys1) { + const val1 = obj1[key]; + const val2 = obj2[key]; + const areObjects = isObject(val1) && isObject(val2); + if ( + areObjects && !isDeepEqual(val1, val2) || + (!areObjects && !areValuesEqual(val1, val2)) + ) { + return false; + } + } + + return true; +} + +function areValuesEqual(val1, val2) { + if (isNullorEmpty(val1) && isNullorEmpty(val2)) { + return true; + } + if (val1 === val2) { + return true + } + return false +} + +function isObject(object) { + return object != null && typeof object === 'object'; +} + async function updateEntireFolder(item, folderId) { if (item.item && !folderId) { @@ -118,8 +219,12 @@ async function updateRequestsInFolder(item, folderId, remoteItem) { let postmanRequestBody = buildRequestBody(items) let remotePostmanBody = buildRequestBody(remoteRequest) if (checkIfDifferent(postmanRequestBody, remotePostmanBody)) { - let newRequestDelete = await new pmAPI.Request(publicRemoteCollectionId).delete(remoteRequest.id) - console.log(`deleting request ${newRequestDelete.data.id}`) + if (remoteRequest) { + console.log(`deleting request ${remoteRequest.name}`) + let newRequestDelete = await new pmAPI.Request(publicRemoteCollectionId).delete(remoteRequest.id) + console.log(`deleted request ${newRequestDelete.data.id}`) + } + postmanRequestBody = buildRequestBody(items) let newRequest = await new pmAPI.Request(publicRemoteCollectionId).create(postmanRequestBody, folderId) console.log(`creating request ${newRequest.data.name}`) } else { diff --git a/postman-script/updateByFolder/postmanAPI.js b/postman-script/updateByFolder/postmanAPI.js index 54a8266..18a5f96 100644 --- a/postman-script/updateByFolder/postmanAPI.js +++ b/postman-script/updateByFolder/postmanAPI.js @@ -31,14 +31,13 @@ class Collection { }) axiosRetry(this.axios, { retries: 10, - retryDelay: axiosRetry.exponentialDelay, retryCondition: (error) => { console.log('error, retrying') return error.code === 'ECONNRESET' || error.code === 'ECONNABORTED' || axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response.status === 429 } }) - this.axios.interceptors.response.use(response => response, handleError); + //this.axios.interceptors.response.use(response => response, handleError); } @@ -94,13 +93,13 @@ class Folder { }) axiosRetry(this.axios, { retries: 10, - retryDelay: axiosRetry.exponentialDelay, + //retryDelay: axiosRetry.exponentialDelay, retryCondition: (error) => { console.log('error, retrying') return error.code === 'ECONNRESET' || error.code === 'ECONNABORTED' || axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response.status === 429 } }) - this.axios.interceptors.response.use(response => response, handleError); + //this.axios.interceptors.response.use(response => response, handleError); } async get (folderId) { @@ -165,13 +164,13 @@ class Request { }) axiosRetry(this.axios, { retries: 10, - retryDelay: axiosRetry.exponentialDelay, + //retryDelay: axiosRetry.exponentialDelay, retryCondition: (error) => { console.log('error, retrying') return error.code === 'ECONNRESET' || error.code === 'ECONNABORTED' || axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response.status === 429 } }) - this.axios.interceptors.response.use(response => response, handleError); + //this.axios.interceptors.response.use(response => response, handleError); } async get (requestId) { @@ -237,13 +236,13 @@ class Response { }) axiosRetry(this.axios, { retries: 10, - retryDelay: axiosRetry.exponentialDelay, + //retryDelay: axiosRetry.exponentialDelay, retryCondition: (error) => { console.log('error, retrying') return error.code === 'ECONNRESET' || error.code === 'ECONNABORTED' || axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response.status === 429 } }) - this.axios.interceptors.response.use(response => response, handleError); + //this.axios.interceptors.response.use(response => response, handleError); } async get (responseId) {