diff --git a/.circleci/build_slug.sh b/.circleci/build_slug.sh deleted file mode 100644 index a1dc87d..0000000 --- a/.circleci/build_slug.sh +++ /dev/null @@ -1,10 +0,0 @@ -echo "Building slug" -id=$(git archive $CIRCLE_BRANCH | docker run -e "NPM_CONFIG_PRODUCTION=false" -i -a stdin elasticio/appbuilder) -docker attach $id -RC=$? -if [ $RC -eq 0 ];then - echo "Build ok." -else - echo "Build failed" - exit 1 -fi diff --git a/circle.yml b/.circleci/config.yml similarity index 92% rename from circle.yml rename to .circleci/config.yml index 0bfeabb..a6e6a4f 100644 --- a/circle.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: test: docker: - - image: circleci/node:8-stretch + - image: circleci/node:12-stretch steps: - checkout - restore_cache: diff --git a/.eslintrc.js b/.eslintrc.js index 0e0e6a6..e6df3d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,14 @@ module.exports = { env: { - browser: true, commonjs: true, es6: true, - mocha: true + mocha: true, }, extends: [ 'airbnb-base', ], + rules: { + 'no-await-in-loop': 0, + 'max-len': ['error', { code: 150 }], + }, }; diff --git a/CHANGELOG.md b/CHANGELOG.md index eb26de3..6934459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0 (September 11, 2020) + +* Component uses new `Auth-client` functionality +* All deprecated triggers and actions are deleted +* Code is refactored ## 1.3.5 (August 21, 2020) diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 94421d0..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = function (grunt) { - - // Project configuration. - grunt.initConfig({ - - "jasmine_node": { - options: { - forceExit: true, - extensions: 'js' - }, - all: ["./spec"] - }, - - jscs: { - src: [ - "lib/**/*.js" - ], - options: { - config: ".jscsrc" - } - } - }); - - grunt.loadNpmTasks('grunt-jasmine-node'); - - grunt.loadNpmTasks("grunt-jscs"); - - grunt.registerTask('test', ['jscs', 'jasmine_node']); - - // Default task - grunt.registerTask('default', ['jscs', 'jasmine_node']); -}; \ No newline at end of file diff --git a/README.md b/README.md index 7c4de50..41b61f8 100644 --- a/README.md +++ b/README.md @@ -1,127 +1,224 @@ -# salesforce-component - -## Description +[![CircleCI](https://circleci.com/gh/elasticio/salesforce-component.svg)](https://circleci.com/gh/elasticio/salesforce-component) +# Salesforce Component +## Table of Contents + +* [General information](#general-information) + * [Description](#description) + * [Completeness Matrix](#completeness-matrix) + * [API version](#api-version) + * [Environment variables](#environment-variables) +* [Credentials](#credentials) +* [Triggers](#triggers) + * [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) + * [Query Trigger](#query-trigger) + * [Subscribe to platform events (REALTIME FLOWS ONLY)](#subscribe-to-platform-events-realtime-flows-only) +* [Actions](#actions) + * [Bulk Create/Update/Delete/Upsert](#bulk-createupdatedeleteupsert) + * [Bulk Query](#bulk-query) + * [Create Object](#create-object) + * [Delete Object (at most 1)](#delete-object-at-most-1) + * [Lookup Object (at most 1)](#lookup-object-at-most-1) + * [Lookup Objects](#lookup-objects) + * [Query Action](#query-action) + * [Upsert Object](#upsert-object) +* [Known Limitations](#known-limitations) + +## General information +### Description [elastic.io](http://www.elastic.io;) iPaaS component that connects to Salesforce API -### Purpose -Salesforce component is designed for Salesforce API integration. - -### Completeness Matrix -![Salesforse-component Completeness Matrix](https://user-images.githubusercontent.com/36419533/75436046-9a5ef880-595c-11ea-838f-32660c119972.png) +### Completeness Matrix +![Salesforse-component Completeness Matrix](https://user-images.githubusercontent.com/16806832/93742890-972ca200-fbf7-11ea-9b7c-4a0aeff1c0fb.png) [Salesforse-component Completeness Matrix](https://docs.google.com/spreadsheets/d/1_4vvDLdQeXqs3c8OxFYE80CvpeSC8e3Wmwl1dcEGO2Q/edit?usp=sharing) - ### API version -The component uses Salesforce - API Version 45.0, except: -- Deprecated Actions and Triggers - API Version 25.0 +The component uses Salesforce - API Version 46.0 by defaults but can be overwritten by the environment variable `SALESFORCE_API_VERSION` + +### Environment variables +Name|Mandatory|Description|Values| +|----|---------|-----------|------| +|LOG_LEVEL| false | Controls logger level | `trace`, `debug`, `info`, `warn`, `error` | +|SALESFORCE_API_VERSION| false | Determines API version of Salesforce to use | Default: `46.0` | +|REFRESH_TOKEN_RETRIES| false | Determines how many retries to refresh token should be done before throwing an error | Default: `10` | +|HASH_LIMIT_TIME| false | Hash expiration time in ms | Default: `600000` | +|HASH_LIMIT_ELEMENTS| false | Hash size number limit | Default: `10` | -### Authentication +## Credentials Authentication occurs via OAuth 2.0. -In the component repository you need to specify OAuth Client credentials as environment variables: -- ```OAUTH_CLIENT_ID``` - your OAuth client key -- ```OAUTH_CLIENT_SECRET``` - your OAuth client secret +In order to make OAuth work, you need a new App in your Salesforce. During app creation process you will be asked to specify +the callback URL, to process OAuth authentication via elastic.io platform your callback URL should be ``https://your-tenant.elastic.io/callback/oauth2``. +More information you can find [here](https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm). -## Create new App in Salesforce +During credentials creation you would need to: +- select existing Auth Client from drop-down list ``Choose Auth Client`` or create the new one. +For creating Auth Client you should specify following fields: + +Field name|Mandatory|Description| +|----|---------|-----------| +|Name| true | your Auth Client's name | +|Client ID| true | your OAuth client key | +|Client Secret| true | your OAuth client secret | +|Authorization Endpoint| true | your OAuth authorization endpoint. For production use `https://login.salesforce.com/services/oauth2/authorize`, for sandbox - `https://test.salesforce.com/services/oauth2/authorize`| +|Token Endpoint| true | your OAuth Token endpoint for refreshing access token. For production use `https://login.salesforce.com/services/oauth2/token`, for sandbox - `https://test.salesforce.com/services/oauth2/token`| + +- fill field ``Name Your Credential`` +- click on ``Authenticate`` button - if you have not logged in Salesforce before then log in by entering data in the login window that appears +- click on ``Verify`` button for verifying your credentials +- click on ``Save`` button for saving your credentials -In order to make OAuth work, you need a new App in your Salesforce. During app creation process you will be asked to specify -the callback URL, to process OAuth authentication via elastic.io platform your callback URL should be +## Triggers +### Get New and Updated Objects Polling +Polls existing and updated objects. You can select any custom or built-in object for your Salesforce instance. + +#### Input field description +* **Object** - Input field where you should select the type of object which updates you want to get. E.g. `Account`; +* **Start Time** - Indicates the beginning time to start polling from. Defaults to `1970-01-01T00:00:00.000Z`; +* **End Time** - If provided, don’t fetch records modified after this time; +* **Size of Polling Page** - Indicates the size of pages to be fetched. You can set positive integer, max `10 000`, defaults to `1000`; +* **Process single page per execution** - You can select on of options (defaults to `yes`): + 1. `yes` - if the number of changed records exceeds the maximum number of results in a page, wait until the next flow start to fetch the next page; + 2. `no` - if the number of changed records exceeds the maximum number of results in a page, the next pages will fetching in the same execution. +* **Include linked objects** - Multiselect dropdown list with all the related child and parent objects of the selected object type. List entries are given as `Object Name/Reference To (Relationship Name)`. Select one or more related objects, which will be join queried and included in the response from your Salesforce Organization. Please see the **Limitations** section below for use case advisories. +* **Output method** - dropdown list with options: `Emit all` - all found records will be emitted in one array `records`, and `Emit individually` - each found object will be emitted individual. Optional field, defaults to: `Emit individually`. +* **Max Fetch Count** - limit for a number of messages that can be fetched. 1,000 is the default value when the variable is not set. +For example, you have 234 “Contact” objects, 213 of them were changed from 2019-01-01. +You want to select all “Contacts” that were changed from 2019-01-01, set the page size to 100 and process single page per execution. +For you purpose you need to specify following fields: + * Object: `Contact` + * Start Time: `2019-01-01T00:00:00.000Z` + * Size of Polling Page: `100` + * Process single page per execution: `yes` (or leave this empty) + +![image](https://user-images.githubusercontent.com/16806832/93762053-8ab84180-fc17-11ea-92da-0fb9669b44f9.png) + +As a result, all contacts will be fetched in three calls of the trigger: two of them by 100 items, and the last one by 13. +If you select `no` in **Process single page per execution**, all 213 contacts will be fetched in one call of the trigger. -```https://your-tenant.elastic.io/callback/oauth2``` +#### Limitations +When a binary field (primitive type `base64`, e.g. Documents, Attachments, etc) is selected on **Include linked objects**, an error will be thrown: 'MALFORMED_QUERY: Binary fields cannot be selected in join queries. Instead of querying objects with binary fields as linked objects (such as children Attachments), try querying them directly.' There is also a limit to the number of linked objects that you can query at once - beyond two or three, depending on the number of fields in the linked objects, Salesforce could potentially return a Status Code 431 or 414 error, meaning the query is too long. Finally, due to a bug with multiselect dropdowns, it is recommended to deselect all of the elements in this field before you change your selection in the *Object* dropdown list. -More information you can find [here](https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm) +### Query Trigger +Continuously runs the same SOQL Query and emits results according to ``Output method`` configuration field. +Use the Salesforce Object Query Language (SOQL) to search your organization’s Salesforce data for specific information. +SOQL is similar to the SELECT statement in the widely used Structured Query Language (SQL) but is designed specifically for Salesforce data. +This trigger allows you to interact with your data using SOQL. -## Credentials +#### List of Expected Config fields -During credentials creation you would need to: -- choose ``Environment`` -- enter ``Username`` and ``Password`` in a pop-up window after click on ``Authenticate`` button. -- verify and save your new credentials. -### Limitations -According to [Salesforce documentation](https://help.salesforce.com/articleView?id=remoteaccess_request_manage.htm&type=5) +* **SOQL Query** - Input field for your SOQL Query +* **Output method** - dropdown list with options: `Emit all` - all found records will be emitted in one array `records`, and `Emit individually` - each found object will be emitted individual. Optional field, defaults to: `Emit individually`. -`Each connected app allows five unique approvals per user. When a sixth approval is made, the oldest approval is revoked.` +### Subscribe to platform events (REALTIME FLOWS ONLY) +This trigger will subscribe for any platform Event using Salesforce streaming API. -You can get error `refresh token has been expired` if the same user account was authenticated with same OAuth Application (OAuth Client) more than 4 times. This is feature of the Salesforce platform that automatically invalidates the oldest refresh_token as soon as a number of given refresh tokens for an individual user account exceeds 4. +#### Input field description +* **Event object name** - Input field where you should select the type of platform event which you want to subscribe E.g. `My platform event` + +#### How to create new custom Platform event Entity: +`Setup --> Integrations --> Platform Events --> New Platform Event` +![Screenshot from 2019-03-11 11-51-10](https://user-images.githubusercontent.com/13310949/54114889-1088e900-43f4-11e9-8b49-3a8113b6577d.png) + +You can find more detail information in the [Platform Events Intro Documentation](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_intro.htm). + +#### Limitations: +At the moment this trigger can be used only for **"Realtime"** flows. ## Actions -### Query -Executing a SOQL Query that may return many objects. Each resulting object is emitted one-by-one. Use the Salesforce Object Query Language (SOQL) to search your organization’s Salesforce data for specific information. SOQL is similar to the SELECT statement in the widely used Structured Query Language (SQL) but is designed specifically for Salesforce data. This action allows you to interact with your data using SOQL. -Empty object will be returned, if query doesn't find any data. +### Bulk Create/Update/Delete/Upsert +Bulk API provides a simple interface for quickly loading large amounts of data from CSV file into Salesforce (up to 10'000 records). +Action takes a CSV file from the attachment as an input. CSV file format is described in the [Salesforce documentatio](https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/datafiles.htm) -#### Input fields description -* **Include deleted** - checkbox, if checked - deleted records will be included into the result list. +#### List of Expected Config fields +* **Operation** - dropdown list with 4 supported operations: `Create`, `Update`, `Upsert` and `Delete`. +* **Object** - dropdown list where you should choose the object type to perform bulk operation. E.g. `Case`. +* **Timeout** - maximum time to wait until the server completes a bulk operation (default: `600` sec). + +#### Expected input metadata +* **External ID Field** - a name of the External ID field for `Upsert` operation. E.g. `my_external_id__c` + +#### Expected output metadata +Result is an object with a property **result**: `array`. It contains objects with 3 fields. +* **id** - `string`, salesforce object id +* **success** - `boolean`, if operation was successful `true` +* **errors** - `array`, if operation failed contains description of errors + +#### Limitations +* No errors thrown in case of failed Object Create/Update/Delete/Upsert (`"success": "false"`). +* Object ID is needed for Update and Delete. +* External ID is needed for Upsert. +* Salesforce processes up to 10'000 records from the input CSV file. + +### Bulk Query +Fetches records to a CSV file. + +#### Expected input metadata -#### Input fields description -* **Optional batch size** - A positive integer specifying batch size. If no batch size is specified then results of the query will be emitted one-by-one, otherwise, query results will be emitted in an array of maximum batch size. -* **Allow all results to be returned in a set** - checkbox which allows emitting query results in a single array. `Optional batch size` option is ignored in this case. * **SOQL Query** - Input field where you should type the SOQL query. E.g. `"SELECT ID, Name from Contact where Name like 'John Smi%'"` -* **Max Fetch Count** - limit for a number of messages that can be fetched. 1,000 is the default value when the variable is not set. + +Result is a CSV file in the attachment. ### Create Object Creates a new Selected Object. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. +Action creates a single object. Note: -In case of an **Attachment** object type you should specify `Body` in base64 encoding. `ParentId` is a Salesforce ID of an object (Account, Lead, Contact) which an attachment is going to be attached to. +In case of an **Attachment** object type you should specify `Body` in base64 encoding. +`ParentId` is a Salesforce ID of an object (Account, Lead, Contact) which an attachment is going to be attached to. -#### Input fields description +#### List of Expected Config fields * **Object** - Input field where you should choose the object type, which you want to find. E.g. `Account` * **Utilize data attachment from previous step (for objects with a binary field)** - a checkbox, if it is checked and an input message contains an attachment and specified object has a binary field (type of base64) then the input data is put into object's binary field. In this case any data specified for the binary field in the data mapper is discarded. This action will automatically retrieve all existing fields of chosen object type that available on your Salesforce organization +#### Expected input metadata +Input metadata is fetched dynamically from your Salesforce account. + +#### Expected output metadata +Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. + #### Limitations When **Utilize data attachment from previous step (for objects with a binary field)** is checked and this action is used with Local Agent error would be thrown: 'getaddrinfo ENOTFOUND steward-service.platform.svc.cluster.local steward-service.platform.svc.cluster.local:8200' ### Delete Object (at most 1) -Deletes an object by a selected field. One can filter by either unique fields or all fields of that sobject. Input metadata is fetched dynamically from your Salesforce account. +Deletes an object by a selected field. One can filter by either unique fields or all fields of that sobject. -#### Input field description +#### List of Expected Config fields * **Object** - dropdown list where you should choose the object type, which you want to find. E.g. `Account`. * **Type Of Search** - dropdown list with two values: `Unique Fields` and `All Fields`. * **Lookup by field** - dropdown list with all fields on the selected object, if on *Type Of Search* is chosen `All Fields`, or with all fields on the selected object where `type` is `id` or `unique` is `true` , if on *Type Of Search* is chosen `Unique Fields` then all searchable fields both custom and standard will be available for selection. +#### Expected input metadata +Input metadata is fetched dynamically from your Salesforce account and depends on field `Lookup by field`. + +#### Expected output metadata Result is an object with 3 fields. * **id** - `string`, salesforce object id * **success** - `boolean`, if operation was successful `true` * **errors** - `array`, if operation fails, it will contain description of errors -#### Metadata description -Metadata for each particular `Object type` + `Lookup by field` is generating dynamically. - -### Upsert Object -Creates or Updates Selected Object. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - -#### Input field description -* **Object** - Input field where you should choose the object type, which you want to find. E.g. `Account` -* **Optional Upsert field** - Input field where you should specify the ExternalID name field. E.g. `ExtId__c`. -* **Utilize data attachment from previous step (for objects with a binary field)** - a checkbox, if it is checked and an input message contains an attachment and specified object has a binary field (type of base64) then the input data is put into object's binary field. In this case any data specified for the binary field in the data mapper is discarded. - -You should specify **external** or **internal Id** for making some updates in salesforce object. -If you want to create new Object you should always specify **Optional Upsert field** and value of ExternalId in input body structure. - -#### Limitations -When **Utilize data attachment from previous step (for objects with a binary field)** is checked and this action is used with Local Agent error would be thrown: 'getaddrinfo ENOTFOUND steward-service.platform.svc.cluster.local steward-service.platform.svc.cluster.local:8200' - ### Lookup Object (at most 1) Lookup an object by a selected field. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. +Action creates a single object. -#### Input field description +#### List of Expected Config fields * **Object** - Dropdown list displaying all searchable object types. Select one type to query, e.g. `Account`. * **Type Of Search** - Dropdown list with two values: `Unique Fields` and `All Fields`. * **Lookup by field** - Dropdown list with all fields on the selected object if the *Type Of Search* is `All Fields`. If the *Type Of Search* is `Unique Fields`, the dropdown lists instead all fields on the selected object where `type` is `id` or `unique` is `true`. -* **Include linked objects** - Multiselect dropdown list with all the related child and parent objects of the selected object type. List entries are given as `Object Name/Reference To (Relationship Name)`. Select one or more related objects, which will be join queried and included in the response from your Salesforce Organization. Please see the **Limitations** section below for use case advisories. +* **Include referenced objects** - Multiselect dropdown list with all the related child and parent objects of the selected object type. List entries are given as `Object Name/Reference To (Relationship Name)`. Select one or more related objects, which will be join queried and included in the response from your Salesforce Organization. Please see the **Limitations** section below for use case advisories. +* **Allow criteria to be omitted** - Checkbox. If checked and nothing is specified in criteria, an empty object will be returned. If not checked and nothing is found, the action will throw an error. * **Allow zero results** - Checkbox. If checked and nothing is found in your Salesforce Organization, an empty object will be returned. If not checked and nothing is found, the action will throw an error. * **Pass binary data to the next component (if found object has it)** - Checkbox. If it is checked and the found object record has a binary field (primitive type `base64`), then its data will be passed to the next component as a binary attachment. * **Enable Cache Usage** - Flag to enable cache usage. -#### Metadata description - +#### Expected input metadata +Input metadata is fetched dynamically from your Salesforce account. Metadata contains one field whose name, type and mandatoriness are generated according to the value of the configuration fields *Lookup by field* and *Allow criteria to be omitted*. +#### Expected output metadata +Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. + #### Limitations When a binary field (primitive type `base64`, e.g. Documents, Attachments, etc) is selected on **Include linked objects**, an error will be thrown: 'MALFORMED_QUERY: Binary fields cannot be selected in join queries. Instead of querying objects with binary fields as linked objects (such as children Attachments), try querying them directly.' There is also a limit to the number of linked objects that you can query at once - beyond two or three, depending on the number of fields in the linked objects, Salesforce could potentially return a Status Code 431 or 414 error, meaning the query is too long. Finally, due to a bug with multiselect dropdowns, it is recommended to deselect all of the elements in this field before you change your selection in the *Object* dropdown list. @@ -136,7 +233,7 @@ This parameters can be changed by setting environment variables: ### Lookup Objects Lookup a list of objects satisfying specified criteria. -#### Input field description +#### List of Expected Config fields * **Object** - dropdown list where you should choose the object type, which you want to find. E.g. `Account`. * **Include deleted** - checkbox, if checked - deleted records will be included into the result list. * **Output method** - dropdown list with following values: "Emit all", "Emit page", "Emit individually". @@ -144,14 +241,7 @@ Lookup a list of objects satisfying specified criteria. * **Enable Cache Usage** - Flag to enable cache usage. * **Max Fetch Count** - limit for a number of messages that can be fetched. 1,000 is the default value when the variable is not set. -#### Note -Action has caching mechanism. By default action stores last 10 request-response pairs for 10 min duration. -This parameters can be changed by setting environment variables: -* **HASH_LIMIT_TIME** - Hash expiration time in milis -* **HASH_LIMIT_ELEMENTS** - Hash size number limit - -#### Metadata description - +#### Expected input metadata Depending on the the configuration field *Output method* the input metadata can contain different fields: *Output method* - "Emit page": Field "Page size" - optional positive integer that defaults to 1000; @@ -175,209 +265,51 @@ Between each two term's group of fields: Field "Logical operator" - one of the following: "AND", "OR"; +#### Expected output metadata Output data is an object, with a field "results" that is an array of objects. -### Bulk Create/Update/Delete/Upsert -Bulk API provides a simple interface for quickly loading large amounts of data from CSV file into Salesforce (up to 10'000 records). -Action takes a CSV file from the attachment as an input. CSV file format is described in the [Salesforce documentatio](https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/datafiles.htm) - -#### Input field description -* **Operation** - dropdown list with 3 supported operations: `Create`, `Update` and `Delete`. -* **Object** - dropdown list where you should choose the object type to perform bulk operation. E.g. `Case`. -* **Timeout** - maximum time to wait until the server completes a bulk operation (default: `600` sec). - -#### Metadata description -* **External ID Field** - a name of the External ID field for `Upsert` operation. E.g. `my_external_id__c` - -Result is an object with a property **result**: `array`. It contains objects with 3 fields. -* **id** - `string`, salesforce object id -* **success** - `boolean`, if operation was successful `true` -* **errors** - `array`, if operation failed contains description of errors - -#### Limitations -* No errors thrown in case of failed Object Create/Update/Delete/Upsert (`"success": "false"`). -* Object ID is needed for Update and Delete. -* External ID is needed for Upsert. -* Salesforce processes up to 10'000 records from the input CSV file. - - -### Bulk Query -Fetches records to a CSV file. - -#### Input field description -* **SOQL Query** - Input field where you should type the SOQL query. E.g. `"SELECT ID, Name from Contact where Name like 'John Smi%'"` - -Result is a CSV file in the attachment. - +#### Note +Action has caching mechanism. By default action stores last 10 request-response pairs for 10 min duration. +This parameters can be changed by setting environment variables: +* **HASH_LIMIT_TIME** - Hash expiration time in milis +* **HASH_LIMIT_ELEMENTS** - Hash size number limit -### Lookup Object (deprecated) -Lookup an object by a selected field. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. +### Query Action +Executing a SOQL Query that may return many objects. +Use the Salesforce Object Query Language (SOQL) to search your organization’s Salesforce data for specific information. +SOQL is similar to the SELECT statement in the widely used Structured Query Language (SQL) but is designed specifically for Salesforce data. +This action allows you to interact with your data using SOQL. +Empty object will be returned, if query doesn't find any data. -#### Input field description +#### List of Expected Config fields * **Optional batch size** - A positive integer specifying batch size. If no batch size is specified then results of the query will be emitted one-by-one, otherwise, query results will be emitted in an array of maximum batch size. -* **Object** - Input field where you should choose the object type, which you want to find. E.g. `Account` -* **Lookup field** - Input field where you should choose the lookup field which you want to use for result filtering. E.g. `Id`. +* **Allow all results to be returned in a set (overwrites 'Optional batch size feature')** - checkbox which allows emitting query results in a single array. `Optional batch size` option is ignored in this case. +* **Include deleted** - checkbox, if checked - deleted records will be included into the result list. * **Max Fetch Count** - limit for a number of messages that can be fetched. 1,000 is the default value when the variable is not set. -```For now, you can specify all unique, lookup, ExternalID/Id fields. ``` - -##### Execution result handling -|Condition | Execution result | -|----------|------------------| -|Lookup failed - we were not able to find any parent object. |Lookup action emits a single message with an empty body.| -|Lookup found a single object, e.g. we were able to identify a parent Account to the Contact|A single message will be emitted, found object will be a body of the message| -|Lookup found multiple objects (that may happen when a lookup is made by non-unique field) | Each found object will be emitted with the separate message| - -### New Account `(deprecated)` -Creates a new Account. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - -#### Input fields description -This action will automatically retrieve all existing fields of `Account` object type that available on your Salesforce organization. - -Action is `deprecated`. You can use [Create Object](#create-object) action instead. - -### New Case `(deprecated)` -Creates a new Case. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - -#### Input fields description -This action will automatically retrieve all existing fields of `Case` object type that available on your Salesforce organization - -Action is `deprecated`. You can use [Create Object](#create-object) action instead. - -### New Contact `(deprecated)` -Creates a new Contact. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - - -#### Input fields description -This action will automatically retrieve all existing fields of `Contact` object type that available on your Salesforce organization - -Action is `deprecated`. You can use [Create Object](#create-object) action instead. - -### New Event `(deprecated)` -Creates a new Event. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - -#### Input fields description -This action will automatically retrieve all existing fields of `Event` object type that available on your Salesforce organization - -Action is `deprecated`. You can use [Create Object](#create-object) action instead. - -### New Lead `(deprecated)` -Creates a new Lead. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - -#### Input fields description -This action will automatically retrieve all existing fields of `Lead` object type that available on your Salesforce organization - -Action is `deprecated`. You can use [Create Object](#create-object) action instead. - -### New Note `(deprecated)` -Creates a new Note. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - -#### Input fields description -This action will automatically retrieve all existing fields of `Note` object type that available on your Salesforce organization - -Action is `deprecated`. You can use [Create Object](#create-object) action instead. - -### New Task `(deprecated)` -Creates a new Task. -Action creates a single object. Input metadata is fetched dynamically from your Salesforce account. Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. - -#### Input fields description -This action will automatically retrieve all existing fields of `Task` object type that available on your Salesforce organization - -Action is `deprecated`. You can use [Create Object](#create-object) action instead. +#### Expected input metadata +* **SOQL Query** - Input field where you should type the SOQL query. E.g. `"SELECT ID, Name from Contact where Name like 'John Smi%'"` -## Triggers -### Query -Continuously runs the same SOQL Query and emits results one-by-one. -Use the Salesforce Object Query Language (SOQL) to search your organization’s Salesforce data for specific information. SOQL is similar to the SELECT statement in the widely used Structured Query Language (SQL) but is designed specifically for Salesforce data. This action allows you to interact with your data using SOQL. +### Upsert Object +Creates or Updates Selected Object. +Action creates a single object. #### List of Expected Config fields +* **Object** - Input field where you should choose the object type, which you want to find. E.g. `Account` +* **Optional Upsert field** - Input field where you should specify the ExternalID name field. E.g. `ExtId__c`. +* **Utilize data attachment from previous step (for objects with a binary field)** - a checkbox, if it is checked and an input message contains an attachment and specified object has a binary field (type of base64) then the input data is put into object's binary field. In this case any data specified for the binary field in the data mapper is discarded. -* **SOQL Query** - Input field for your SOQL Query -* **Output method** - dropdown list with options: `Emit all` - all found records will be emitted in one array `records`, and `Emit individually` - each found object will be emitted individual. Optional field, defaults to: `Emit individually`. +You should specify **external** or **internal Id** for making some updates in salesforce object. +If you want to create new Object you should always specify **Optional Upsert field** and value of ExternalId in input body structure. -NOTE: Max possible fetch size is 2000 objects per execution. +#### Expected input metadata +Input metadata is fetched dynamically from your Salesforce account. -### Get New and Updated Objects Polling -Polls existing and updated objects. You can select any custom or built-in object for your Salesforce instance. - -#### Input field description -* **Object** - Input field where you should select the type of object which updates you want to get. E.g. `Account`; -* **Start Time** - Indicates the beginning time to start polling from. Defaults to `1970-01-01T00:00:00.000Z`; -* **End Time** - If provided, don’t fetch records modified after this time; -* **Size of Polling Page** - Indicates the size of pages to be fetched. You can set positive integer, max `10 000`, defaults to `1000`; -* **Process single page per execution** - You can select on of options (defaults to `yes`): - 1. `yes` - if the number of changed records exceeds the maximum number of results in a page, wait until the next flow start to fetch the next page; - 2. `no` - if the number of changed records exceeds the maximum number of results in a page, the next pages will fetching in the same execution. -* **Include linked objects** - Multiselect dropdown list with all the related child and parent objects of the selected object type. List entries are given as `Object Name/Reference To (Relationship Name)`. Select one or more related objects, which will be join queried and included in the response from your Salesforce Organization. Please see the **Limitations** section below for use case advisories. -* **Output method** - dropdown list with options: `Emit all` - all found records will be emitted in one array `records`, and `Emit individually` - each found object will be emitted individual. Optional field, defaults to: `Emit individually`. -* **Max Fetch Count** - limit for a number of messages that can be fetched. 1,000 is the default value when the variable is not set. -For example, you have 234 “Contact” objects, 213 of them were changed from 2019-01-01. -You want to select all “Contacts” that were changed from 2019-01-01, set the page size to 100 and process single page per execution. -For you purpose you need to specify following fields: - * Object: `Contact` - * Start Time: `2019-01-01T00:00:00.000Z` - * Size of Polling Page: `100` - * Process single page per execution: `yes` (or leave this empty) -![image](https://user-images.githubusercontent.com/16806832/55322499-30f11400-5485-11e9-81da-50518f76258c.png) - -As a result, all contacts will be fetched in three calls of the trigger: two of them by 100 items, and the last one by 13. -If you select `no` in **Process single page per execution**, all 213 contacts will be fetched in one call of the trigger. +#### Expected output metadata +Output metadata is the same as input metadata, so you may expect all fields that you mapped as input to be returned as output. #### Limitations -When a binary field (primitive type `base64`, e.g. Documents, Attachments, etc) is selected on **Include linked objects**, an error will be thrown: 'MALFORMED_QUERY: Binary fields cannot be selected in join queries. Instead of querying objects with binary fields as linked objects (such as children Attachments), try querying them directly.' There is also a limit to the number of linked objects that you can query at once - beyond two or three, depending on the number of fields in the linked objects, Salesforce could potentially return a Status Code 431 or 414 error, meaning the query is too long. Finally, due to a bug with multiselect dropdowns, it is recommended to deselect all of the elements in this field before you change your selection in the *Object* dropdown list. - -### Subscribe to platform events (REALTIME FLOWS ONLY) -This trigger will subscribe for any platform Event using Salesforce streaming API. - -#### Input field description -* **Event object name** - Input field where you should select the type of platform event which you want to subscribe E.g. `My platform event` - -#### How to create new custom Platform event Entity: -`Setup --> Integrations --> Platform Events --> New Platform Event` -![Screenshot from 2019-03-11 11-51-10](https://user-images.githubusercontent.com/13310949/54114889-1088e900-43f4-11e9-8b49-3a8113b6577d.png) - -You can find more detail information in the [Platform Events Intro Documentation](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_intro.htm). -#### Environment Variables - -1. `SALESFORCE_API_VERSION` - API version for not deprecated actions and triggers e.g(46.0), default value 45.0 - -2. `LOG_LEVEL` - `trace` | `debug` | `info` | `warning` | `error` controls logger level - -#### Limitations: -At the moment this trigger can be used only for **"Realtime"** flows. - -### New Case `(deprecated)` -Polls existing and updated Cases (fetches a maximum of 1000 objects per execution) - -Trigger is `deprecated`. You can use [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) action instead. - -### New Lead `(deprecated)` -Polls existing and updated Leads (fetches a maximum of 1000 objects per execution) - -Trigger is `deprecated`. You can use [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) action instead. - -### New Contact `(deprecated)` -Polls existing and updated Contacts (fetches a maximum of 1000 objects per execution) - -Trigger is `deprecated`. You can use [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) action instead. - -### New Account `(deprecated)` -Polls existing and updated Accounts (fetches a maximum of 1000 objects per execution) - -Trigger is `deprecated`. You can use [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) action instead. - -### New Task `(deprecated)` -Polls existing and updated Tasks (fetches a maximum of 1000 objects per execution) - -Trigger is `deprecated`. You can use [Get New and Updated Objects Polling](#get-new-and-updated-objects-polling) action instead. +When **Utilize data attachment from previous step (for objects with a binary field)** is checked and this action is used with Local Agent error would be thrown: 'getaddrinfo ENOTFOUND steward-service.platform.svc.cluster.local steward-service.platform.svc.cluster.local:8200' ## Known limitations Attachments mechanism does not work with [Local Agent Installation](https://support.elastic.io/support/solutions/articles/14000076461-announcing-the-local-agent-) diff --git a/component.json b/component.json index 04c0123..2a2d7f5 100644 --- a/component.json +++ b/component.json @@ -4,18 +4,11 @@ "docsUrl": "https://github.com/elasticio/salesforce-component", "url": "http://www.salesforce.com/", "buildType": "docker", + "authClientTypes": ["oauth2"], "envVars": { - "OAUTH_CLIENT_ID": { - "required": true, - "description": "Your Salesforce OAuth client key" - }, - "OAUTH_CLIENT_SECRET": { - "required": true, - "description": "Your Salesforce OAuth client secret" - }, "SALESFORCE_API_VERSION": { "required": true, - "description": "Salesforce API version to use for non deprecated methods. Default 46.0" + "description": "Salesforce API version. Default 46.0" }, "HASH_LIMIT_TIME": { "required": false, @@ -26,65 +19,26 @@ "description": "Hash size number limit" } }, - "useOAuthClient": true, "credentials": { "fields": { - "prodEnv": { - "label": "Environment", - "viewClass": "SelectView", - "required": true, - "model": { - "test": "Sandbox", - "login": "Production" - }, - "prompt": "Select environment" - }, "oauth": { "label": "Authentication", "viewClass": "OAuthFieldView", "required": true } - }, - "oauth2": { - "client_id": "{{OAUTH_CLIENT_ID}}", - "client_secret": "{{OAUTH_CLIENT_SECRET}}", - "auth_uri": "https://{{prodEnv}}.salesforce.com/services/oauth2/authorize", - "token_uri": "https://{{prodEnv}}.salesforce.com/services/oauth2/token" } }, "triggers": { - "queryTrigger": { - "title": "Query", - "main": "./lib/triggers/query.js", - "type": "polling", - "description": "Will continuously run the same SOQL Query and emit results one-by-one", - "metadata": { - "out": {} - }, - "fields": { - "query": { - "label": "SOQL Query", - "required": true, - "viewClass": "TextAreaView" - }, - "outputMethod": { - "viewClass": "SelectView", - "label": "Output method", - "required": false, - "model": { - "emitAll": "Emit all", - "emitIndividually": "Emit individually" - }, - "prompt": "Please select an output method. Defaults to: Emit individually" - } - } - }, "entry": { "title": "Get New and Updated Objects Polling", "main": "./lib/entry.js", + "order": 99, + "help" : { + "description": "Will poll for existing and updated objects where you can select any custom or build-in object for your Salesforce instance", + "link": "/components/salesforce/triggers#get-new-and-updated-objects-polling-trigger" + }, "type": "polling", "dynamicMetadata": true, - "description": "Will poll for existing and updated objects where you can select any custom or build-in object for your Salesforce instance", "fields": { "object": { "viewClass": "SelectView", @@ -150,9 +104,43 @@ } } }, + "queryTrigger": { + "title": "Query", + "main": "./lib/triggers/query.js", + "type": "polling", + "order": 98, + "help" : { + "description": "Will continuously run the same SOQL Query and emit results", + "link": "/components/salesforce/triggers#query-trigger" + }, + "metadata": { + "out": {} + }, + "fields": { + "query": { + "label": "SOQL Query", + "required": true, + "viewClass": "TextAreaView" + }, + "outputMethod": { + "viewClass": "SelectView", + "label": "Output method", + "required": false, + "model": { + "emitAll": "Emit all", + "emitIndividually": "Emit individually" + }, + "prompt": "Please select an output method. Defaults to: Emit individually" + } + } + }, "streamPlatformEvents": { "title": "Subscribe to platform events (REALTIME FLOWS ONLY)", - "description": "Can be used for subscription to the specified in the configuration Platform Event object. Can be used only for Realtime flows", + "order": 97, + "help" : { + "description": "Can be used for subscription to the specified in the configuration Platform Event object. Can be used only for Realtime flows", + "link": "/components/salesforce/triggers#subscribe-to-platform-events-trigger" + }, "main": "./lib/triggers/streamPlatformEvents.js", "type": "polling", "fields": { @@ -164,122 +152,69 @@ "prompt": "Please select a Event object" } } - }, - "newCase": { - "deprecated": true, - "title": "New Case", - "main": "./lib/triggers/case.js", - "type": "polling", - "description": "Trigger is deprecated. You can use Get New and Updated Objects Polling action instead.", - "dynamicMetadata": true - }, - "newLead": { - "deprecated": true, - "title": "New Lead", - "main": "./lib/triggers/lead.js", - "type": "polling", - "description": "Trigger is deprecated. You can use Get New and Updated Objects Polling action instead.", - "dynamicMetadata": true - }, - "newContact": { - "deprecated": true, - "title": "New Contact", - "main": "./lib/triggers/contact.js", - "type": "polling", - "description": "Trigger is deprecated. You can use Get New and Updated Objects Polling action instead.", - "dynamicMetadata": true - }, - "newAccount": { - "deprecated": true, - "title": "New Account", - "main": "./lib/triggers/account.js", - "type": "polling", - "description": "Trigger is deprecated. You can use Get New and Updated Objects Polling action instead.", - "dynamicMetadata": true - }, - "newTask": { - "deprecated": true, - "title": "New Task", - "main": "./lib/triggers/task.js", - "type": "polling", - "description": "Trigger is deprecated. You can use Get New and Updated Objects Polling action instead.", - "dynamicMetadata": true } }, "actions": { - "queryAction": { - "title": "Query", - "main": "./lib/actions/query.js", - "description": "Executing an SOQL Query that may return many objects. Each resulting object is emitted one-by-one", - "fields": { - "batchSize": { - "viewClass": "TextFieldView", - "label": "Optional batch size", - "required": false, - "note": "A positive integer specifying batch size. If no batch size is specified then results of the query will be emitted one-by-one, otherwise query results will be emitted in array of maximum batch size.", - "placeholder": "0" - }, - "allowResultAsSet": { - "label": "Allow all results to be returned in a set (overwrites 'Optional batch size feature')", - "viewClass": "CheckBoxView" - }, - "includeDeleted": { - "viewClass": "CheckBoxView", - "label": "Include deleted" - }, - "maxFetch": { - "label": "Max Fetch Count", - "required": false, - "viewClass": "TextFieldView", - "placeholder": "1000", - "note": "Limit for a number of messages that can be fetched, 1,000 by default, up to 2000" - } + "bulk_cud": { + "title": "Bulk Create/Update/Delete/Upsert", + "main": "./lib/actions/bulk_cud.js", + "order": 99, + "help" : { + "description": "Bulk operations on objects in CSV file", + "link": "/components/salesforce/actions#bulk-createupdatedeleteupsert-action" }, - "metadata": { - "in": { - "type": "object", - "properties": { - "query": { - "maxLength": 20000, - "title": "SOQL Query", - "type": "string", - "required": true - } - } - }, - "out": {} - } - }, - "upsert": { - "title": "Upsert Object", - "main": "./lib/actions/upsert.js", - "description": "Create or Update Selected Object", - "dynamicMetadata": true, "fields": { + "operation": { + "viewClass": "SelectView", + "label": "Operation", + "required": true, + "model": { + "insert": "Create", + "update": "Update", + "delete": "Delete", + "upsert": "Upsert" + }, + "prompt": "Please select an operation" + }, "sobject": { "viewClass": "SelectView", "label": "Object", "required": true, + "require": ["operation"], "model": "objectTypes", "prompt": "Please select a Salesforce Object" }, - "extIdField": { + "timeout": { "viewClass": "TextFieldView", - "label": "Optional Upsert field", - "required": false, - "note": "Please make sure selected SObject has this field and it is marked as 'External ID'", - "placeholder": "extID__c" - }, - "utilizeAttachment": { - "viewClass": "CheckBoxView", - "label": "Utilize data attachment from previous step (for objects with a binary field)" + "label": "Timeout for operation (sec)", + "required": true, + "note": "A positive integer specifying timeout in seconds. Maximum Salesforce's server timeout for the bulk operations is 10 min (600 sec).", + "placeholder": "600" } + }, + "dynamicMetadata": true + }, + "bulk_q": { + "title": "Bulk Query", + "main": "./lib/actions/bulk_q.js", + "order": 98, + "help" : { + "description": "Bulk query with the results in CSV file", + "link": "/components/salesforce/actions#bulk-query-action" + }, + "metadata": { + "in": "./lib/schemas/bulk_q.in.json", + "out": "./lib/schemas/bulk_q.out.json" } }, "create": { "title": "Create Object", "main": "./lib/actions/createObject.js", - "description": "Creates new Selected Object", + "order": 97, + "help" : { + "description": "Creates new Selected Object", + "link": "/components/salesforce/actions#create-object-action" + }, "dynamicMetadata": true, "fields": { "sobject": { @@ -298,7 +233,11 @@ "delete": { "title": "Delete Object (at most 1)", "main": "./lib/actions/deleteObject.js", - "description": "Delete Selected Object", + "order": 96, + "help" : { + "description": "Delete Selected Object", + "link": "/components/salesforce/actions#delete-object-action-at-most-1" + }, "dynamicMetadata": true, "fields": { "sobject": { @@ -330,7 +269,11 @@ "lookupObject": { "title": "Lookup Object (at most 1)", "main": "./lib/actions/lookupObject.js", - "description": "Lookup object (at most 1) by selected field", + "order": 95, + "help" : { + "description": "Lookup object (at most 1) by selected field", + "link": "/components/salesforce/actions#lookup-object-action-at-most-1" + }, "dynamicMetadata": true, "fields": { "sobject": { @@ -366,7 +309,7 @@ "model": "getLinkedObjectsModel", "order": 5, "prompt": "Please select the related objects you want included in your lookup" - }, + }, "allowCriteriaToBeOmitted": { "viewClass": "CheckBoxView", "label": "Allow criteria to be omitted", @@ -392,7 +335,11 @@ "lookupObjects": { "title": "Lookup Objects", "main": "./lib/actions/lookupObjects.js", - "description": "Look for objects satisfying specified criteria", + "order": 94, + "help" : { + "description": "Look for objects satisfying specified criteria", + "link": "/components/salesforce/actions#lookup-objects-action" + }, "dynamicMetadata": true, "fields": { "sobject": { @@ -437,76 +384,15 @@ } } }, - "account": { - "deprecated": true, - "title": "New Account", - "main": "./lib/actions/account.js", - "description": "Action is deprecated. You can use Create Object action instead.", - "dynamicMetadata": true - }, - "case": { - "deprecated": true, - "title": "New Case", - "main": "./lib/actions/case.js", - "description": "Action is deprecated. You can use Create Object action instead.", - "dynamicMetadata": true - }, - "contact": { - "deprecated": true, - "title": "New Contact", - "main": "./lib/actions/contact.js", - "description": "Action is deprecated. You can use Create Object action instead.", - "dynamicMetadata": true - }, - "event": { - "deprecated": true, - "title": "New Event", - "main": "./lib/actions/event.js", - "description": "Action is deprecated. You can use Create Object action instead.", - "dynamicMetadata": true - }, - "lead": { - "deprecated": true, - "title": "New Lead", - "main": "./lib/actions/lead.js", - "description": "Action is deprecated. You can use Create Object action instead.", - "dynamicMetadata": true - }, - "note": { - "deprecated": true, - "title": "New Note", - "main": "./lib/actions/note.js", - "description": "Action is deprecated. You can use Create Object action instead.", - "dynamicMetadata": true - }, - "task": { - "deprecated": true, - "title": "New Task", - "main": "./lib/actions/task.js", - "description": "Action is deprecated. You can use Create Object action instead.", - "dynamicMetadata": true - }, - "lookup": { - "deprecated": true, - "title": "Lookup Object", - "main": "./lib/actions/lookup.js", - "description": "Lookup object by selected field", - "dynamicMetadata": true, + "queryAction": { + "title": "Query", + "main": "./lib/actions/query.js", + "order": 93, + "help" : { + "description": "Executing an SOQL Query that may return many objects. Each resulting object is emitted one-by-one", + "link": "/components/salesforce/actions#query-action" + }, "fields": { - "sobject": { - "viewClass": "SelectView", - "label": "Object", - "required": true, - "model": "objectTypes", - "prompt": "Please select a Salesforce Object" - }, - "lookupField": { - "viewClass": "SelectView", - "label": "Lookup by field", - "required": true, - "model": "getLookupFieldsModel", - "note": "Please select the field which you want to use for lookup" - }, "batchSize": { "viewClass": "TextFieldView", "label": "Optional batch size", @@ -514,57 +400,65 @@ "note": "A positive integer specifying batch size. If no batch size is specified then results of the query will be emitted one-by-one, otherwise query results will be emitted in array of maximum batch size.", "placeholder": "0" }, + "allowResultAsSet": { + "label": "Allow all results to be returned in a set (overwrites 'Optional batch size feature')", + "viewClass": "CheckBoxView" + }, + "includeDeleted": { + "viewClass": "CheckBoxView", + "label": "Include deleted" + }, "maxFetch": { "label": "Max Fetch Count", "required": false, "viewClass": "TextFieldView", "placeholder": "1000", - "note": "Limit for a number of messages that can be fetched, 1,000 by default" + "note": "Limit for a number of messages that can be fetched, 1,000 by default, up to 2000" } + }, + "metadata": { + "in": { + "type": "object", + "properties": { + "query": { + "maxLength": 20000, + "title": "SOQL Query", + "type": "string", + "required": true + } + } + }, + "out": {} } }, - "bulk_cud": { - "title": "Bulk Create/Update/Delete/Upsert", - "main": "./lib/actions/bulk_cud.js", - "description": "Bulk operations on objects in CSV file", + "upsert": { + "title": "Upsert Object", + "main": "./lib/actions/upsert.js", + "order": 92, + "help" : { + "description": "Create or Update Selected Object", + "link": "/components/salesforce/actions#upsert-object-action" + }, + "dynamicMetadata": true, "fields": { - "operation": { - "viewClass": "SelectView", - "label": "Operation", - "required": true, - "model": { - "insert": "Create", - "update": "Update", - "delete": "Delete", - "upsert": "Upsert" - }, - "prompt": "Please select an operation" - }, "sobject": { "viewClass": "SelectView", "label": "Object", "required": true, - "require": ["operation"], "model": "objectTypes", "prompt": "Please select a Salesforce Object" }, - "timeout": { + "extIdField": { "viewClass": "TextFieldView", - "label": "Timeout for operation (sec)", - "required": true, - "note": "A positive integer specifying timeout in seconds. Maximum Salesforce's server timeout for the bulk operations is 10 min (600 sec).", - "placeholder": "600" + "label": "Optional Upsert field", + "required": false, + "note": "Please make sure selected SObject has this field and it is marked as 'External ID'", + "placeholder": "extID__c" + }, + "utilizeAttachment": { + "viewClass": "CheckBoxView", + "label": "Utilize data attachment from previous step (for objects with a binary field)" } - }, - "dynamicMetadata": true - }, - "bulk_q": { - "title": "Bulk Query", - "main": "./lib/actions/bulk_q.js", - "description": "Bulk query with the results in CSV file", - "metadata": { - "in": "./lib/schemas/bulk_q.in.json", - "out": "./lib/schemas/bulk_q.out.json" } } } diff --git a/lib/actions/account.js b/lib/actions/account.js deleted file mode 100644 index 9f858f3..0000000 --- a/lib/actions/account.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildAction('Account', exports, '25.0'); diff --git a/lib/actions/bulk_cud.js b/lib/actions/bulk_cud.js index 68f0bc6..5bb929c 100644 --- a/lib/actions/bulk_cud.js +++ b/lib/actions/bulk_cud.js @@ -1,40 +1,27 @@ const { messages } = require('elasticio-node'); -const util = require('../util'); - const { Readable } = require('stream'); - -const MetaLoader = require('../helpers/metaLoader'); -const sfConnection = require('../helpers/sfConnection.js'); +const util = require('../util'); +const { callJSForceMethod } = require('../helpers/wrapper'); const DEFAULT_TIMEOUT = 600; // 10 min - exports.objectTypes = async function objectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - switch (configuration.sobject) { + switch (configuration.operation) { case 'insert': { - return metaLoader.getCreateableObjectTypes(); + return callJSForceMethod.call(this, configuration, 'getCreateableObjectTypes'); } case 'update': { - return metaLoader.getUpdateableObjectTypes(); - } - case 'upsert': { - return metaLoader.getObjectTypes(); + return callJSForceMethod.call(this, configuration, 'getUpdateableObjectTypes'); } default: { - // 'delete' operation or anything else - return metaLoader.getObjectTypes(); + // 'delete' and 'upsert' operation or anything else + return callJSForceMethod.call(this, configuration, 'getObjectTypes'); } } }; - exports.process = async function bulkCUD(message, configuration) { - this.logger.debug('Starting:', configuration.operation); - - const conn = sfConnection.createConnection(configuration, this); - - // Bulk operation ('insert', 'update', 'delete', 'upsert') + this.logger.info('Starting Bulk %s action', configuration.operation); // Get CSV from attachment if (!message.attachments || Object.keys(message.attachments).length === 0) { this.logger.error('Attachment not found'); @@ -49,7 +36,7 @@ exports.process = async function bulkCUD(message, configuration) { timeout = DEFAULT_TIMEOUT; } - let result = await util.downloadAttachment(message.attachments[key].url); + const result = await util.downloadAttachment(message.attachments[key].url); const csvStream = new Readable(); csvStream.push(result); @@ -59,15 +46,19 @@ exports.process = async function bulkCUD(message, configuration) { if (configuration.operation === 'upsert') { extra = { extIdField: message.body.extIdField }; } - // Create job - const job = conn.bulk.createJob(configuration.sobject, configuration.operation, extra); - const batch = job.createBatch(); + const batchOptions = { + sobject: configuration.sobject, + operation: configuration.operation, + extra, + }; + const job = await callJSForceMethod.call(this, configuration, 'bulkCreateJob', batchOptions); + const batch = job.createBatch(); return new Promise((resolve, reject) => { // Upload CSV to SF batch.execute(csvStream) // eslint-disable-next-line no-unused-vars - .on('queue', (batchInfo) => { + .on('queue', () => { // Check while job status become JobComplete or Failed, Aborted batch.poll(1000, timeout * 1000); }).on('response', (rets) => { @@ -85,7 +76,6 @@ exports.process = async function bulkCUD(message, configuration) { }); }; - exports.getMetaModel = async function getMetaModel(configuration) { const meta = { in: { diff --git a/lib/actions/bulk_q.js b/lib/actions/bulk_q.js index 6dbb3cb..2474988 100644 --- a/lib/actions/bulk_q.js +++ b/lib/actions/bulk_q.js @@ -1,18 +1,11 @@ const { messages } = require('elasticio-node'); const client = require('elasticio-rest-node')(); - const request = require('request'); - -const sfConnection = require('../helpers/sfConnection.js'); - +const { callJSForceMethod } = require('../helpers/wrapper'); exports.process = async function bulkQuery(message, configuration) { - this.logger.error('Starting: query'); - - const conn = sfConnection.createConnection(configuration, this); - + this.logger.info('Starting Bulk Query action'); const signedUrl = await client.resources.storage.createSignedUrl(); - const out = messages.newEmptyMessage(); out.attachments = { 'bulk_query.csv': { @@ -21,23 +14,14 @@ exports.process = async function bulkQuery(message, configuration) { }, }; out.body = {}; - + const stream = await callJSForceMethod.call(this, configuration, 'bulkQuery', message.body.query); return new Promise((resolve, reject) => { - // Bulk operation ('query') - const stream = conn.bulk.query(message.body.query) - .on('error', (err) => { - this.logger.debug('error query:', err); - reject(err); - }) - .stream(); - - // upload csv attachment stream.pipe(request.put(signedUrl.put_url, (err, resp, body) => { if (err) { - this.logger.debug('error upload:', err); + this.logger.error('Error upload query results'); reject(err); } else { - this.logger.debug('success'); + this.logger.info('Action successfully processed'); out.body = { result: body }; resolve(out); } diff --git a/lib/actions/case.js b/lib/actions/case.js deleted file mode 100644 index f77cf92..0000000 --- a/lib/actions/case.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildAction('Case', exports, '25.0'); diff --git a/lib/actions/contact.js b/lib/actions/contact.js deleted file mode 100644 index 6f22c44..0000000 --- a/lib/actions/contact.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildAction('Contact', exports, '25.0'); diff --git a/lib/actions/createObject.js b/lib/actions/createObject.js index 869fa10..16f3e75 100644 --- a/lib/actions/createObject.js +++ b/lib/actions/createObject.js @@ -1,60 +1,34 @@ -const _ = require('lodash'); const { messages } = require('elasticio-node'); -const { SalesforceEntity } = require('../entry.js'); -const MetaLoader = require('../helpers/metaLoader'); -const sfConnection = require('../helpers/sfConnection.js'); +const { processMeta } = require('../helpers/utils'); const attachment = require('../helpers/attachment.js'); +const { callJSForceMethod } = require('../helpers/wrapper'); -exports.objectTypes = function objectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getCreateableObjectTypes(); +exports.objectTypes = async function objectTypes(configuration) { + return callJSForceMethod.call(this, configuration, 'getCreateableObjectTypes'); }; -exports.process = async function createObject(message, configuration) { - this.logger.info(`Preparing to create a ${configuration.sobject} object...`); - - const sfConn = sfConnection.createConnection(configuration, this); - - this.logger.debug('Creating message body: ', message.body); +exports.getMetaModel = async function getMetaModel(configuration) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + return processMeta(meta, 'create'); +}; - const binaryField = await attachment.prepareBinaryData(message, configuration, sfConn, this); +exports.process = async function createObject(message, configuration) { + this.logger.info('Starting Create Object Action'); + this.logger.debug(`Preparing to create a ${configuration.sobject} object...`); + const binaryField = await attachment.prepareBinaryData(message, configuration, this); this.logger.info('Sending request to SalesForce...'); + const response = await callJSForceMethod.call(this, configuration, 'sobjectCreate', message); - try { - const response = await sfConn.sobject(configuration.sobject).create(message.body); + this.logger.debug(`${configuration.sobject} has been successfully created`); + this.logger.trace(`${configuration.sobject} has been successfully created (ID = ${response.id}).`); + // eslint-disable-next-line no-param-reassign + message.body.id = response.id; - this.logger.debug('SF response: ', response); - this.logger.info(`${configuration.sobject} has been successfully created (ID = ${response.id}).`); + if (binaryField) { // eslint-disable-next-line no-param-reassign - message.body.id = response.id; - - if (binaryField) { - // eslint-disable-next-line no-param-reassign - delete message.body[binaryField.name]; - } - - return messages.newMessageWithBody(message.body); - } catch (err) { - return this.emit('error', err); + delete message.body[binaryField.name]; } -}; -exports.getMetaModel = function getMetaModel(cfg, cb) { - const entity = new SalesforceEntity(this); - entity.getInMetaModel(cfg, (err, data) => { - if (err) { - return cb(err); - } - // eslint-disable-next-line no-param-reassign - data.out = _.cloneDeep(data.in); - // eslint-disable-next-line no-param-reassign - data.out.properties.id = { - type: 'string', - required: true, - readonly: true, - title: 'ObjectID', - }; - return cb(null, data); - }); + return messages.newMessageWithBody(message.body); }; diff --git a/lib/actions/deleteObject.js b/lib/actions/deleteObject.js index 2859045..208b95e 100644 --- a/lib/actions/deleteObject.js +++ b/lib/actions/deleteObject.js @@ -1,36 +1,42 @@ /* eslint-disable no-param-reassign,consistent-return */ const { messages } = require('elasticio-node'); -const MetaLoader = require('../helpers/metaLoader'); -const sfConnection = require('../helpers/sfConnection.js'); -const helpers = require('../helpers/deleteObjectHelpers.js'); +const { callJSForceMethod } = require('../helpers/wrapper'); +const { processMeta, getLookupFieldsModelWithTypeOfSearch, TYPES_MAP } = require('../helpers/utils'); -/** - * Des: Taken from lookupObject.js due to overlapping functionality -*/ -module.exports.objectTypes = function objectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getObjectTypes(); +module.exports.objectTypes = async function objectTypes(configuration) { + return callJSForceMethod.call(this, configuration, 'getObjectTypes'); +}; + +module.exports.getLookupFieldsModel = async function getLookupFieldsModel(configuration) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + return getLookupFieldsModelWithTypeOfSearch(meta, configuration.typeOfSearch); }; module.exports.getMetaModel = async function getMetaModel(configuration) { - this.logger.debug(`Get MetaModel is called with config ${JSON.stringify(configuration)}`); - configuration.metaType = 'lookup'; - const metaLoader = new MetaLoader(configuration, this); - const metaData = await metaLoader.loadMetadata(); - if (configuration.lookupField) { /* use the new feature */ + let metaData; + if (configuration.lookupField) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + metaData = await processMeta(meta, 'lookup', configuration.lookupField); metaData.in.properties[configuration.lookupField].required = true; } else { - metaData.in.properties = { - id: { - title: 'Object ID', - type: 'string', - required: true, + metaData = { + in: { + type: 'object', + properties: { + id: { + title: 'Object ID', + type: 'string', + required: true, + }, + }, }, }; - metaData.out.properties = { - result: { - title: 'name', + } + metaData.out = { + type: 'object', + properties: { + response: { type: 'object', properties: { id: { @@ -47,76 +53,61 @@ module.exports.getMetaModel = async function getMetaModel(configuration) { }, }, }, - }; - } + }, + }; return metaData; }; module.exports.process = async function process(message, configuration) { + this.logger.info('Starting Delete Object (at most 1) Action'); const { lookupField } = configuration; const lookupValue = message.body[lookupField]; - const res = []; - const sfConn = sfConnection.createConnection(configuration, this); + let Id; if (!lookupValue) { - this.logger.trace('No unique criteria provided, run previous functionality'); + this.logger.debug('No unique criteria provided, run previous functionality'); if (!message.body.id || !String(message.body.id).trim()) { this.logger.error('Salesforce error. Empty ID'); return messages.newEmptyMessage(); } - + Id = message.body.id; + } else { this.logger.debug(`Preparing to delete a ${configuration.sobject} object...`); - let response; - try { - response = await sfConn.sobject(configuration.sobject).delete(message.body.id); - } catch (err) { - this.logger.error(`Salesforce error. ${err.message}`); - return messages.newEmptyMessage(); - } - - this.logger.debug(`${configuration.sobject} has been successfully deleted (ID = ${response.id}).`); - return messages.newMessageWithBody({ response }); - } - - this.logger.trace(`Preparing to delete a ${configuration.sobject} object...`); + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + const field = meta.fields.find((fld) => fld.name === lookupField); + const condition = (['date', 'datetime'].includes(field.type) + || TYPES_MAP[field.type] === 'number' + || TYPES_MAP[field.type] === 'boolean') + ? `${lookupField} = ${lookupValue}` + : `${lookupField} = '${lookupValue}'`; - const meta = await sfConn.describe(configuration.sobject); - const field = meta.fields.find(fld => fld.name === lookupField); - const condition = (['date', 'datetime'].includes(field.type) - || MetaLoader.TYPES_MAP[field.type] === 'number' - || MetaLoader.TYPES_MAP[field.type] === 'boolean') - ? `${lookupField} = ${lookupValue}` - : `${lookupField} = '${lookupValue}'`; - - await sfConn.sobject(configuration.sobject) - .select('*') - .where(condition) - .on('record', (record) => { - res.push(record); - }) - .on('end', async () => { - if (res.length === 0) { + const results = await callJSForceMethod.call(this, configuration, 'selectQuery', { condition }); + if (results.length === 1) { + // eslint-disable-next-line prefer-destructuring + Id = results[0].Id; + } else { + if (results.length === 0) { this.logger.info('No objects are found'); - await this.emit('data', messages.newEmptyMessage()); - } else if (res.length === 1) { - await helpers.deleteObjById.call(this, sfConn, res[0].Id, configuration.sobject); - } else { - const err = new Error('More than one object found, can only delete 1'); - this.logger.error(err); - this.logger.trace(`Here are the objects found ${JSON.stringify(res)}`); - await this.emit('error', err); + return messages.newEmptyMessage(); } - }) - .on('error', async (err) => { + const err = new Error('More than one object found, can only delete 1'); this.logger.error(err); - await this.emit('error', err); - }) - .run({ autoFetch: true, maxFetch: 2 }); -}; + throw err; + } + } + this.logger.debug(`Preparing to delete a ${configuration.sobject} object...`); + + let response; + try { + response = await callJSForceMethod.call(this, configuration, 'sobjectDelete', { id: Id }); + } catch (err) { + this.logger.error('Salesforce error occurred'); + return messages.newEmptyMessage(); + } -module.exports.getLookupFieldsModel = function getLookupFieldsModel(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getLookupFieldsModelWithTypeOfSearch(configuration.typeOfSearch); + this.logger.debug(`${configuration.sobject} has been successfully deleted`); + this.logger.trace(`${configuration.sobject} has been successfully deleted (ID = ${response.id}).`); + return messages.newMessageWithBody({ response }); }; diff --git a/lib/actions/event.js b/lib/actions/event.js deleted file mode 100644 index c67a540..0000000 --- a/lib/actions/event.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildAction('Event', exports, '25.0'); diff --git a/lib/actions/lead.js b/lib/actions/lead.js deleted file mode 100644 index 95b73f8..0000000 --- a/lib/actions/lead.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildAction('Lead', exports, '25.0'); diff --git a/lib/actions/lookup.js b/lib/actions/lookup.js deleted file mode 100644 index e4271e1..0000000 --- a/lib/actions/lookup.js +++ /dev/null @@ -1,94 +0,0 @@ -const jsforce = require('jsforce'); -const { messages } = require('elasticio-node'); -const MetaLoader = require('../helpers/metaLoader'); -const common = require('../common.js'); - -/** - * This function will return a metamodel description for a particular object - * - * @param configuration - */ -module.exports.getMetaModel = async function getMetaModel(configuration) { - // eslint-disable-next-line no-param-reassign - configuration.metaType = 'lookup'; - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.loadMetadata(); -}; - -/** - * This function will return a metamodel description for a particular object - * - * @param configuration - */ -module.exports.getLookupFieldsModel = async function getLookupFieldsModel(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getLookupFieldsModel(); -}; - -/** - * See list on https://na45.salesforce.com/services/data/ - * @type {string} - */ -module.exports.process = async function processAction(message, configuration) { - const batchSize = configuration.batchSize || 0; - this.logger.info('batchSize', batchSize); - const res = []; - const conn = new jsforce.Connection({ - oauth2: { - clientId: process.env.OAUTH_CLIENT_ID, - clientSecret: process.env.OAUTH_CLIENT_SECRET, - }, - instanceUrl: configuration.oauth.instance_url, - accessToken: configuration.oauth.access_token, - refreshToken: configuration.oauth.refresh_token, - version: common.globalConsts.SALESFORCE_API_VERSION, - }); - - conn.on('refresh', (accessToken, refreshResult) => { - this.logger.trace('Keys were updated, res=%j', refreshResult); - this.emit('updateKeys', { oauth: refreshResult }); - }); - - const maxFetch = configuration.maxFetch || 1000; - - await conn.sobject(configuration.sobject) - .select('*') - .where(`${configuration.lookupField} = '${message.body[configuration.lookupField]}'`) - .on('record', (record) => { - res.push(record); - }) - .on('end', () => { - if (!res.length) { - this.emit('data', messages.newMessageWithBody({})); - } - if (batchSize > 0) { - while (res.length) { - const result = res.splice(0, batchSize); - this.logger.debug('emitting batch %j', { result }); - this.emit('data', messages.newMessageWithBody({ result })); - } - } else { - res.forEach((record) => { - this.logger.debug('emitting record %j', record); - this.emit('data', messages.newMessageWithBody(record)); - }); - } - }) - .on('error', (err) => { - this.logger.error(err); - this.emit('error', err); - }) - .execute({ autoFetch: true, maxFetch }); -}; - -/** - * This function will be called to fetch available object types. - * - * Note: only updatable and creatable object types are returned here - * - * @param configuration - */ -module.exports.objectTypes = async function getObjectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getObjectTypes(); -}; diff --git a/lib/actions/lookupObject.js b/lib/actions/lookupObject.js index b0b79e0..4ebe898 100644 --- a/lib/actions/lookupObject.js +++ b/lib/actions/lookupObject.js @@ -1,8 +1,10 @@ const { messages } = require('elasticio-node'); -const MetaLoader = require('../helpers/metaLoader'); -const sfConnection = require('../helpers/sfConnection.js'); const attachmentTools = require('../helpers/attachment.js'); const { lookupCache } = require('../helpers/lookupCache.js'); +const { + processMeta, getLookupFieldsModelWithTypeOfSearch, getLinkedObjectTypes, TYPES_MAP, +} = require('../helpers/utils'); +const { callJSForceMethod } = require('../helpers/wrapper'); /** * This function will return a metamodel description for a particular object @@ -10,12 +12,9 @@ const { lookupCache } = require('../helpers/lookupCache.js'); * @param configuration */ module.exports.getMetaModel = async function getMetaModel(configuration) { - // eslint-disable-next-line no-param-reassign - configuration.metaType = 'lookup'; - const metaLoader = new MetaLoader(configuration, this); - const metaData = await metaLoader.loadMetadata(); - metaData.in - .properties[configuration.lookupField].required = !configuration.allowCriteriaToBeOmitted; + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + const metaData = await processMeta(meta, 'lookup', configuration.lookupField); + metaData.in.properties[configuration.lookupField].required = !configuration.allowCriteriaToBeOmitted; return metaData; }; @@ -24,9 +23,9 @@ module.exports.getMetaModel = async function getMetaModel(configuration) { * * @param configuration */ -module.exports.getLookupFieldsModel = function getLookupFieldsModel(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getLookupFieldsModelWithTypeOfSearch(configuration.typeOfSearch); +module.exports.getLookupFieldsModel = async function getLookupFieldsModel(configuration) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + return getLookupFieldsModelWithTypeOfSearch(meta, configuration.typeOfSearch); }; /** @@ -34,9 +33,20 @@ module.exports.getLookupFieldsModel = function getLookupFieldsModel(configuratio * * @param configuration */ -module.exports.getLinkedObjectsModel = function getLinkedObjectsModel(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getLinkedObjectsModel(); +module.exports.getLinkedObjectsModel = async function getLinkedObjectsModel(configuration) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + return getLinkedObjectTypes(meta); +}; + +/** + * This function will be called to fetch available object types. + * + * Note: only updatable and creatable object types are returned here + * + * @param configuration + */ +module.exports.objectTypes = async function getObjectTypes(configuration) { + return callJSForceMethod.call(this, configuration, 'getSearchableObjectTypes'); }; /** @@ -46,6 +56,7 @@ module.exports.getLinkedObjectsModel = function getLinkedObjectsModel(configurat * @param configuration - contains configuration data for processing */ module.exports.process = async function processAction(message, configuration) { + this.logger.info('Starting Lookup Object (at most 1) Action'); const { allowCriteriaToBeOmitted, allowZeroResults, @@ -53,34 +64,31 @@ module.exports.process = async function processAction(message, configuration) { linkedObjects = [], } = configuration; const lookupValue = message.body[lookupField]; - const res = []; - const conn = sfConnection.createConnection(configuration, this); if (!lookupValue) { if (allowCriteriaToBeOmitted) { - this.emit('data', messages.newMessageWithBody({})); + await this.emit('data', messages.newMessageWithBody({})); return; } const err = new Error('No unique criteria provided'); this.logger.error(err); - this.emit('error', err); - return; + throw err; } - const meta = await conn.describe(configuration.sobject); - const field = meta.fields.find(fld => fld.name === lookupField); - const condition = (['date', 'datetime'].includes(field.type) || MetaLoader.TYPES_MAP[field.type] === 'number') + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + const field = meta.fields.find((fld) => fld.name === lookupField); + const whereCondition = (['date', 'datetime'].includes(field.type) || TYPES_MAP[field.type] === 'number') ? `${lookupField} = ${lookupValue}` : `${lookupField} = '${lookupValue}'`; lookupCache.useCache(configuration.enableCacheUsage); - const queryKey = lookupCache.generateKeyFromDataArray(configuration.sobject, condition); + const queryKey = lookupCache.generateKeyFromDataArray(configuration.sobject, whereCondition); this.logger.trace(`Current request key hash: "${queryKey}"`); if (lookupCache.hasKey(queryKey)) { this.logger.info('Cached response found!'); const response = lookupCache.getResponse(queryKey); - // eslint-disable-next-line consistent-return - return this.emit('data', messages.newMessageWithBody(response)); + await this.emit('data', messages.newMessageWithBody(response)); + return; } // the query for the object and all its linked parent objects @@ -90,82 +98,41 @@ module.exports.process = async function processAction(message, configuration) { return query; }, ''); - // selecting the object and all its parents - let query = conn.sobject(configuration.sobject) - .select(selectedObjects); - - // the query for all the linked child objects - query = linkedObjects.reduce((newQuery, obj) => { - if (obj.startsWith('!')) { - return newQuery.include(obj.slice(1)) - .select('*') - .end(); - } - return newQuery; - }, query); - - query = query.where(condition) - .on('error', (err) => { - this.logger.error(err); - if (err.message === 'Binary fields cannot be selected in join queries') { - // eslint-disable-next-line no-param-reassign - err.message = 'Binary fields cannot be selected in join queries. ' - + 'Instead of querying objects with binary fields as linked objects ' - + '(such as children Attachments), try querying them directly.'; - } - this.emit('error', err); - }); - - query.on('record', (record) => { - res.push(record); - }); - - query.on('end', async () => { - if (res.length === 0) { - if (allowZeroResults) { - lookupCache.addRequestResponsePair(queryKey, {}); - this.emit('data', messages.newMessageWithBody({})); - } else { - const err = new Error('No objects found'); - this.logger.error(err); - this.emit('error', err); - } - } else if (res.length === 1) { - try { - const outputMessage = messages.newMessageWithBody(res[0]); - - if (configuration.passBinaryData) { - const attachment = await attachmentTools.getAttachment(configuration, res[0], conn, this); - if (attachment) { - outputMessage.attachments = attachment; - } - } + const queryOptions = { + selectedObjects, linkedObjects, whereCondition, maxFetch: 2, + }; + const records = await callJSForceMethod.call(this, configuration, 'pollingSelectQuery', queryOptions); - lookupCache.addRequestResponsePair(queryKey, res[0]); - this.logger.debug('emitting record %j', outputMessage); - this.emit('data', outputMessage); - } catch (err) { - this.logger.error(err); - this.emit('error', err); - } + if (records.length === 0) { + if (allowZeroResults) { + lookupCache.addRequestResponsePair(queryKey, {}); + await this.emit('data', messages.newMessageWithBody({})); } else { - const err = new Error('More than one object found'); + const err = new Error('No objects found'); this.logger.error(err); - this.emit('error', err); + throw (err); } - }); - - await query.execute({ autoFetch: true, maxFetch: 2 }); -}; + } else if (records.length === 1) { + try { + const outputMessage = messages.newMessageWithBody(records[0]); + + if (configuration.passBinaryData) { + const attachment = await attachmentTools.getAttachment(configuration, records[0], this); + if (attachment) { + outputMessage.attachments = attachment; + } + } -/** - * This function will be called to fetch available object types. - * - * Note: only updatable and creatable object types are returned here - * - * @param configuration - */ -module.exports.objectTypes = function getObjectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getSearchableObjectTypes(); + lookupCache.addRequestResponsePair(queryKey, records[0]); + this.logger.debug('Emitting record'); + await this.emit('data', outputMessage); + } catch (err) { + this.logger.error('Lookup Object error occurred'); + throw (err); + } + } else { + const err = new Error('More than one object found'); + this.logger.error(err); + throw (err); + } }; diff --git a/lib/actions/lookupObjects.js b/lib/actions/lookupObjects.js index aa1fb7a..3f5ddbc 100644 --- a/lib/actions/lookupObjects.js +++ b/lib/actions/lookupObjects.js @@ -1,8 +1,8 @@ -const _ = require('lodash'); +/* eslint-disable no-await-in-loop */ const { messages } = require('elasticio-node'); -const MetaLoader = require('../helpers/metaLoader.js'); -const sfConnection = require('../helpers/sfConnection.js'); const { lookupCache } = require('../helpers/lookupCache.js'); +const { callJSForceMethod } = require('../helpers/wrapper'); +const { createProperty } = require('../helpers/utils'); const DEFAULT_PAGE_NUM = 0; const DEFAULT_LIMIT_EMITSINGLE = 10000; @@ -35,9 +35,8 @@ function isNumberInInterval(num, min, max) { return !(Number.isNaN(num) || num < min || num > max); } -module.exports.objectTypes = function getObjectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getSearchableObjectTypes(); +module.exports.objectTypes = async function getObjectTypes(configuration) { + return callJSForceMethod.call(this, configuration, 'getSearchableObjectTypes'); }; module.exports.getMetaModel = async function getMetaModel(configuration) { @@ -77,8 +76,7 @@ module.exports.getMetaModel = async function getMetaModel(configuration) { }; } - const metaLoader = new MetaLoader(configuration, this); - const objectFieldsMetaData = await metaLoader.getObjectFieldsMetaData(); + const objectFieldsMetaData = await callJSForceMethod.call(this, configuration, 'getObjectFieldsMetaData'); const filterableFields = []; objectFieldsMetaData.forEach((field) => { @@ -86,8 +84,7 @@ module.exports.getMetaModel = async function getMetaModel(configuration) { if (field.filterable && field.type !== 'address' && field.type !== 'location') { // Filter out compound fields filterableFields.push(field.label); } - - result.out.properties.results.properties[field.name] = metaLoader.createProperty(field); + result.out.properties.results.properties[field.name] = createProperty(field); } }); @@ -137,7 +134,7 @@ module.exports.getMetaModel = async function getMetaModel(configuration) { return result; }; -async function getWherePart(message, configuration, sfConn) { +async function getWherePart(message, configuration) { let wherePart = ''; const termNumber = parseInt(configuration.termNumber, 10); @@ -146,8 +143,7 @@ async function getWherePart(message, configuration, sfConn) { return wherePart; } - const metaLoader = new MetaLoader(configuration, this, sfConn); - const objMetaData = await metaLoader.ObjectMetaData(); + const objMetaData = await callJSForceMethod.call(this, configuration, 'objectMetaData'); for (let i = 1; i <= termNumber; i += 1) { const sTerm = message.body[`sTerm_${i}`]; @@ -185,17 +181,14 @@ async function getWherePart(message, configuration, sfConn) { } module.exports.process = async function processAction(message, configuration) { + this.logger.info('Starting Lookup Objects Action'); const limitEmitsingle = configuration.maxFetch || DEFAULT_LIMIT_EMITSINGLE; const limitEmitall = configuration.maxFetch || DEFAULT_LIMIT_EMITALL; - this.logger.info(`Preparing to query ${configuration.sobject} objects...`); - - const sfConn = sfConnection.createConnection(configuration, this); - - this.logger.info('Building a query...'); - - const wherePart = await getWherePart(message, configuration, sfConn); - this.logger.debug('Where part: ', wherePart); + this.logger.debug(`Preparing to query ${configuration.sobject} objects...`); + this.logger.debug('Building a wherePart...'); + const wherePart = await getWherePart.call(this, message, configuration); + this.logger.trace('Where part: ', wherePart); let limit; let offset; @@ -220,66 +213,31 @@ module.exports.process = async function processAction(message, configuration) { default: } + let records; lookupCache.useCache(configuration.enableCacheUsage); const queryKey = lookupCache.generateKeyFromDataArray(configuration.sobject, wherePart, offset, limit, configuration.includeDeleted); this.logger.trace(`Current request key hash: "${queryKey}"`); if (lookupCache.hasKey(queryKey)) { this.logger.info('Cached response found!'); - const responseArray = lookupCache.getResponse(queryKey); - if (configuration.outputMethod === 'emitIndividually') { - if (responseArray.length === 0) { - return this.emit('data', messages.newMessageWithBody({ results: [] })); - } - - for (let i = 0; i < responseArray.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - await this.emit('data', messages.newMessageWithBody({ results: [responseArray[i]] })); - } - return true; + records = lookupCache.getResponse(queryKey); + } else { + const queryOptions = { wherePart, offset, limit }; + records = await callJSForceMethod.call(this, configuration, 'lookupQuery', queryOptions); + if (records.length > 0) { + lookupCache.addRequestResponsePair(queryKey, records); } - - return this.emit('data', messages.newMessageWithBody({ results: responseArray })); } - - const records = []; - - const query = sfConn.sobject(configuration.sobject) - .select('*') - .where(wherePart) - .offset(offset) - .limit(limit) - .scanAll(configuration.includeDeleted) - .on('error', (err) => { - const errExt = _.cloneDeep(err); - errExt.message = `Salesforce returned an error: ${err.message}`; - this.emit('error', errExt); - }); - + this.logger.debug(`Got ${records.length} records`); if (configuration.outputMethod === 'emitIndividually') { - query.on('record', (record) => { - records.push(record); - this.emit('data', messages.newMessageWithBody({ results: [record] })); - }) - .on('end', () => { - if (!query.totalFetched) { - this.emit('data', messages.newMessageWithBody({ results: [] })); - } - lookupCache.addRequestResponsePair(queryKey, records); - - this.logger.info(`Got ${query.totalFetched} records`); - }); + if (records.length === 0) { + await this.emit('data', messages.newMessageWithBody({ results: [] })); + } else { + for (let i = 0; i < records.length; i += 1) { + await this.emit('data', messages.newMessageWithBody({ results: [records[i]] })); + } + } } else { - query.on('record', (record) => { - records.push(record); - }) - .on('end', () => { - lookupCache.addRequestResponsePair(queryKey, records); - this.emit('data', messages.newMessageWithBody({ results: records })); - this.logger.info(`Got ${query.totalFetched} records`); - }); + await this.emit('data', messages.newMessageWithBody({ results: records })); } - - this.logger.info('Sending the request to SalesForce...'); - return query.execute({ autoFetch: true, maxFetch: limit }); }; diff --git a/lib/actions/note.js b/lib/actions/note.js deleted file mode 100644 index 21e2f1a..0000000 --- a/lib/actions/note.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildAction('Note', exports, '25.0'); diff --git a/lib/actions/query.js b/lib/actions/query.js index 3451238..3b40ebb 100644 --- a/lib/actions/query.js +++ b/lib/actions/query.js @@ -1,147 +1,46 @@ -const jsforce = require('jsforce'); const { messages } = require('elasticio-node'); -const common = require('../common.js'); - - -function getConnection(configuration) { - const conn = new jsforce.Connection({ - oauth2: { - clientId: process.env.OAUTH_CLIENT_ID, - clientSecret: process.env.OAUTH_CLIENT_SECRET, - }, - instanceUrl: configuration.oauth.instance_url, - accessToken: configuration.oauth.access_token, - refreshToken: configuration.oauth.refresh_token, - version: common.globalConsts.SALESFORCE_API_VERSION, - }); - conn.on('refresh', (accessToken, res) => { - this.logger.info('Keys were updated, res=%j', res); - this.emit('updateKeys', { oauth: res }); - }); - return conn; -} - - -async function emitBatch(message, configuration) { - const self = this; - const { logger } = this; - let batch = []; - const promises = []; - const connection = getConnection.call(self, configuration); - const maxFetch = configuration.maxFetch || 1000; - await new Promise((resolve, reject) => { - const response = connection.query(message.body.query) - .scanAll(configuration.includeDeleted) - .on('record', (record) => { - batch.push(record); - if (batch.length >= configuration.batchSize) { - logger.info('Ready batch: %j', batch); - promises.push(self.emit('data', messages.newMessageWithBody({ result: batch }))); - batch = []; - } - }) - .on('end', () => { - if (response.totalFetched === 0) { - promises.push(self.emit('data', messages.newMessageWithBody({}))); - } - if (batch.length > 0) { - logger.info('Last batch: %j', batch); - promises.push(self.emit('data', messages.newMessageWithBody({ result: batch }))); - } - logger.info('Total in database=%s', response.totalSize); - logger.info('Total fetched=%s', response.totalFetched); - resolve(); - }) - .on('error', (err) => { - logger.error(err); - promises.push(self.emit('error', err)); - reject(err); - }) - .run({ autoFetch: true, maxFetch }); - }); - await Promise.all(promises); -} - -async function emitIndividually(message, configuration) { - const self = this; - const { logger } = this; - const promises = []; - const connection = getConnection.call(self, configuration); - const maxFetch = configuration.maxFetch || 1000; - await new Promise((resolve, reject) => { - const response = connection.query(message.body.query) - .scanAll(configuration.includeDeleted) - .on('record', (record) => { - logger.info('Emitting record: %j', record); - promises.push(self.emit('data', messages.newMessageWithBody(record))); - }) - .on('end', () => { - if (response.totalFetched === 0) { - promises.push(self.emit('data', messages.newMessageWithBody({}))); - } - logger.info('Total in database=%s', response.totalSize); - logger.info('Total fetched=%s', response.totalFetched); - resolve(); - }) - .on('error', (err) => { - logger.error(err); - promises.push(self.emit('error', err)); - reject(err); - }) - .run({ autoFetch: true, maxFetch }); - }); - await Promise.all(promises); -} - -async function emitAll(message, configuration) { - const self = this; - const { logger } = this; - const result = []; - const connection = getConnection.call(self, configuration); - await new Promise((resolve, reject) => { - const response = connection.query(message.body.query) - .scanAll(configuration.includeDeleted) - .on('record', (record) => { - result.push(record); - }) - .on('end', () => { - logger.info('Result: %j', result); - logger.info('Total in database=%s', response.totalSize); - logger.info('Total fetched=%s', response.totalFetched); - if (response.totalFetched === 0) { - resolve(self.emit('data', messages.newMessageWithBody({}))); - } - if (result.length > 0) { - resolve(self.emit('data', messages.newMessageWithBody({ result }))); - } - }) - .on('error', (err) => { - logger.error(err); - reject(self.emit('error', err)); - }); - }); -} +const { callJSForceMethod } = require('../helpers/wrapper'); exports.process = async function processAction(message, configuration) { - const { logger } = this; - logger.trace('Input configuration: %j', configuration); - logger.trace('Input message: %j', message); + this.logger.info('Starting Query Action'); const batchSize = configuration.batchSize || 0; // eslint-disable-next-line no-restricted-globals if (isNaN(batchSize)) { throw new Error('batchSize must be a number'); } - logger.info('Starting SOQL Select batchSize=%s query=%s', batchSize, message.body.query); + const { query } = message.body; + this.logger.trace('Starting SOQL Select batchSize=%s query=%s', batchSize, query); if (configuration.allowResultAsSet) { - logger.info('Selected EmitAllHandler'); - await emitAll.call(this, message, configuration); + this.logger.info('Selected EmitAllHandler'); + const result = await callJSForceMethod.call(this, configuration, 'queryEmitAll', query); + if (result.length === 0) { + await this.emit('data', messages.newEmptyMessage()); + } else { + await this.emit('data', messages.newMessageWithBody({ result })); + } return; } if (configuration.batchSize > 0) { - logger.info('Selected EmitBatchHandler'); - await emitBatch.call(this, message, configuration); - return; + this.logger.info('Selected EmitBatchHandler'); + const results = await callJSForceMethod.call(this, configuration, 'queryEmitBatch', query); + if (results.length === 0) { + await this.emit('data', messages.newEmptyMessage()); + } else { + // eslint-disable-next-line no-restricted-syntax + for (const result of results) { + await this.emit('data', messages.newMessageWithBody({ result })); + } + } + } else { + this.logger.info('Selected EmitIndividuallyHandler'); + const results = await callJSForceMethod.call(this, configuration, 'queryEmitIndividually', query); + if (results.length === 0) { + await this.emit('data', messages.newEmptyMessage()); + } else { + // eslint-disable-next-line no-restricted-syntax + for (const result of results) { + await this.emit('data', messages.newMessageWithBody(result)); + } + } } - logger.info('Selected EmitIndividuallyHandler'); - await emitIndividually.call(this, message, configuration); }; diff --git a/lib/actions/task.js b/lib/actions/task.js deleted file mode 100644 index 2574364..0000000 --- a/lib/actions/task.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildAction('Task', exports, '25.0'); diff --git a/lib/actions/upsert.js b/lib/actions/upsert.js index 5c77578..a283863 100644 --- a/lib/actions/upsert.js +++ b/lib/actions/upsert.js @@ -1,18 +1,17 @@ -const lookup = require('./lookup'); -const MetaLoader = require('../helpers/metaLoader'); -const attachment = require('../helpers/attachment.js'); -const sfConnection = require('../helpers/sfConnection.js'); +/* eslint-disable no-param-reassign */ +const { messages } = require('elasticio-node'); +const { callJSForceMethod } = require('../helpers/wrapper'); +const { processMeta } = require('../helpers/utils'); +const attachment = require('../helpers/attachment'); /** * This function will return a metamodel description for a particular object * * @param configuration */ -module.exports.getMetaModel = function getMetaModel(configuration) { - // eslint-disable-next-line no-param-reassign - configuration.metaType = 'upsert'; - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.loadMetadata(); +module.exports.getMetaModel = async function getMetaModel(configuration) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + return processMeta(meta, 'upsert'); }; /** @@ -22,9 +21,8 @@ module.exports.getMetaModel = function getMetaModel(configuration) { * * @param configuration */ -module.exports.objectTypes = function getObjectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getObjectTypes(); +module.exports.objectTypes = async function getObjectTypes(configuration) { + return callJSForceMethod.call(this, configuration, 'getObjectTypes'); }; /** @@ -34,38 +32,34 @@ module.exports.objectTypes = function getObjectTypes(configuration) { * @param configuration */ module.exports.process = async function upsertObject(message, configuration) { - // eslint-disable-next-line no-param-reassign - configuration.object = configuration.sobject; - this.logger.info('Starting upsertObject'); - - const conn = sfConnection.createConnection(configuration, this); - - await attachment.prepareBinaryData(message, configuration, conn, this); - + this.logger.info('Starting Upsert Object Action'); + await attachment.prepareBinaryData(message, configuration, this); + const { sobject } = configuration; if (message.body.Id) { - this.logger.info('Upserting sobject=%s by internalId', configuration.sobject, message.body.Id); - this.logger.debug('Upserting %s by internalId data: %j', configuration.sobject, message.body.Id, message); - return conn.sobject(configuration.sobject).update(message.body) - .then(() => { - // eslint-disable-next-line no-param-reassign - configuration.lookupField = 'Id'; - return lookup.process.call(this, { body: { Id: message.body.Id } }, - configuration); - }); + configuration.lookupField = 'Id'; + this.logger.info('Upserting sobject=%s by internalId', sobject); + this.logger.trace('Upserting sobject=%s by internalId=%s, data: %j', sobject, message.body.Id, message); + await callJSForceMethod.call(this, configuration, 'sobjectUpdate', message); + } else { + if (!configuration.extIdField) { + throw Error('Can not find internalId/externalId ids'); + } + configuration.lookupField = configuration.extIdField; + this.logger.info('Upserting sobject=%s by externalId', sobject); + this.logger.trace('Upserting sobject=%s by externalId=%s, data: %j', sobject, configuration.extIdField, message); + await callJSForceMethod.call(this, configuration, 'sobjectUpsert', message); } - this.logger.info('Upserting sobject: %s by externalId: %s', configuration.sobject, configuration.extIdField); - this.logger.debug('Upserting sobject: %s by externalId:%s data: %j', configuration.sobject, configuration.extIdField, message); - - if (!configuration.extIdField) { - throw Error('Can not find internalId/externalId ids'); + const lookupResults = await callJSForceMethod.call(this, configuration, 'sobjectLookup', message); + if (lookupResults.length === 1) { + this.logger.info('sobject=%s was successfully upserted by %s', sobject, configuration.lookupField); + this.logger.trace('sobject=%s was successfully upserted by %s=%s', sobject, configuration.lookupField, message.body[configuration.lookupField]); + await this.emit('data', messages.newMessageWithBody(lookupResults[0])); + return; + } + if (lookupResults.length > 1) { + throw new Error(`Found more than 1 sobject=${sobject} by ${configuration.lookupField}=${message.body[configuration.lookupField]}`); + } else { + throw new Error(`Can't found sobject=${sobject} by ${configuration.lookupField}=${message.body[configuration.lookupField]}`); } - - return conn.sobject(configuration.sobject) - .upsert(message.body, configuration.extIdField) - .then(() => { - // eslint-disable-next-line no-param-reassign - configuration.lookupField = configuration.extIdField; - return lookup.process.call(this, message, configuration); - }); }; diff --git a/lib/common.js b/lib/common.js index 480f655..ffe6821 100644 --- a/lib/common.js +++ b/lib/common.js @@ -1,5 +1,4 @@ - - module.exports.globalConsts = { SALESFORCE_API_VERSION: process.env.SALESFORCE_API_VERSION || '46.0', + REFRESH_TOKEN_RETRIES: process.env.REFRESH_TOKEN_RETRIES ? parseInt(process.env.REFRESH_TOKEN_RETRIES, 10) : 10, }; diff --git a/lib/entry.js b/lib/entry.js index 733aa8d..a7b2bac 100644 --- a/lib/entry.js +++ b/lib/entry.js @@ -1,420 +1,107 @@ -/* eslint-disable no-use-before-define,no-param-reassign,no-await-in-loop */ -const _ = require('lodash'); -const util = require('util'); -const Q = require('q'); -const jsforce = require('jsforce'); +/* eslint-disable no-param-reassign,no-await-in-loop */ const elasticio = require('elasticio-node'); const { messages } = elasticio; -const httpUtils = require('./helpers/http-utils'); -const converter = require('./helpers/metadata'); -const describe = require('./helpers/describe'); -const MetaLoader = require('./helpers/metaLoader'); -const fetchObjectsQuery = require('./helpers/objectFetcherQuery'); -const createPresentableError = require('./helpers/error.js'); -const oAuthUtils = require('./helpers/oauth-utils.js'); -const common = require('./common.js'); +const { callJSForceMethod } = require('./helpers/wrapper'); +const { getLinkedObjectTypes, processMeta } = require('./helpers/utils'); -exports.SalesforceEntity = SalesforceEntity; - -function SalesforceEntity(callScope) { - const self = this; - - /** - * This function refreshes salesforce token - * @param conf - configuration with so that conf.oauth.refresh_token should be available - * @param next - callback that will be called with (err, conf) parameters - */ - this.refreshToken = function refreshToken(conf, next) { - oAuthUtils.refreshAppToken(callScope.logger, 'salesforce', conf, - // eslint-disable-next-line consistent-return - (err, newConf) => { - if (err) { - return next(err); - } - callScope.emit('updateKeys', { oauth: newConf.oauth }); - next(null, newConf); - }); - }; +module.exports.objectTypes = async function getObjectTypes(configuration) { + return callJSForceMethod.call(this, configuration, 'getObjectTypes'); +}; - this.getInMetaModel = function getInMetaModel(cfg, cb) { - getMetaModel('in', cfg, cb); - }; +module.exports.linkedObjectTypes = async function linkedObjectTypes(configuration) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + return getLinkedObjectTypes(meta); +}; - this.getOutMetaModel = function getOutMetaModel(cfg, cb) { - getMetaModel('out', cfg, cb); - }; +module.exports.getMetaModel = async function getMetaModel(configuration) { + const meta = await callJSForceMethod.call(this, configuration, 'describe'); + return processMeta(meta, 'polling'); +}; - function getMetaModel(direction, cfg, cb) { - // eslint-disable-next-line consistent-return - self.refreshToken(cfg, (err, newCfg) => { - if (err) { - callScope.logger.error('Error refreshing token', err); - return cb(err); - } - describe.getObjectDescription(callScope.logger, newCfg, - (errInner, objectDescription) => { - if (errInner) { - cb(errInner); - } else { - const metadata = {}; - metadata[direction] = converter.buildSchemaFromDescription( - objectDescription, direction, - ); - cb(null, metadata); - } - }); - }); +module.exports.process = async function process(message, configuration, snapshot) { + this.logger.info('Starting Get New and Updated Objects Polling trigger'); + if (!snapshot || (typeof (snapshot) === 'object' && !snapshot.previousLastModified)) { + snapshot = { + previousLastModified: configuration.startTime || '1970-01-01T00:00:00.000Z', + }; + this.logger.trace('Created snapshot: %j', snapshot); + } else if (typeof snapshot === 'string') { + // for backward compatibility + snapshot = { previousLastModified: snapshot }; + this.logger.trace('Snapshot has been converted to: %j', snapshot); + } else { + this.logger.trace('Got snapshot: %j', snapshot); } - /** - * Polling function that will pull changes from Salesforce - * - * @param msg - message is null - * @param cfg - configuration - * @param snapshot - */ - this.processTrigger = async function processTrigger(msg, cfg, snapshot) { - const { - linkedObjects = [], - } = cfg; - - if (!snapshot || (typeof (snapshot) === 'object' && !snapshot.previousLastModified)) { - snapshot = { - previousLastModified: cfg.startTime || '1970-01-01T00:00:00.000Z', - }; - callScope.logger.info('Created snapshot: %j', snapshot); - } else if (typeof snapshot === 'string') { - // for backward compatibility - snapshot = { previousLastModified: snapshot }; - callScope.logger.info('Snapshot has been converted to: %j', snapshot); + configuration.sobject = configuration.object; + const { linkedObjects = [] } = configuration; + const singlePagePerInterval = (configuration.singlePagePerInterval !== 'no'); + const maxTime = configuration.endTime ? ` AND LastModifiedDate <= ${configuration.endTime}` : ''; + let hasMorePages = true; + let lastSeenTime = snapshot.previousLastModified; + let maxFetch = configuration.maxFetch ? configuration.parseInt(configuration.maxFetch, 10) : 1000; + + if (configuration.sizeOfPollingPage) { + this.logger.debug('Current sizeOfPollingPage=%s, maxFetch=%s', configuration.sizeOfPollingPage, maxFetch); + const sizeOfPollingPage = parseInt(configuration.sizeOfPollingPage, 10); + if (sizeOfPollingPage && sizeOfPollingPage > 0 && sizeOfPollingPage <= 10000) { + maxFetch = sizeOfPollingPage; } else { - callScope.logger.info('Got snapshot: %j', snapshot); - } - - const conn = new jsforce.Connection({ - oauth2: { - clientId: process.env.OAUTH_CLIENT_ID, - clientSecret: process.env.OAUTH_CLIENT_SECRET, - }, - instanceUrl: cfg.oauth.instance_url, - accessToken: cfg.oauth.access_token, - refreshToken: cfg.oauth.refresh_token, - version: common.globalConsts.SALESFORCE_API_VERSION, - }); - conn.on('refresh', (accessToken, res) => { - callScope.logger.trace('Keys were updated, res=%j', res); - emitKeys({ oauth: res }); - }); - - let maxFetch = cfg.maxFetch ? cfg.parseInt(cfg.maxFetch, 10) : 1000; - - if (cfg.sizeOfPollingPage) { - callScope.logger.info('Current sizeOfPollingPage=%s, maxFetch=%s', cfg.sizeOfPollingPage, maxFetch); - const sizeOfPollingPage = parseInt(cfg.sizeOfPollingPage, 10); - if (sizeOfPollingPage && sizeOfPollingPage > 0 && sizeOfPollingPage <= 10000) { - maxFetch = sizeOfPollingPage; - } else { - emitError('Size of Polling Page needs to be positive integer, max 10000 objects'); - emitEnd(); - return; - } - } - const singlePagePerInterval = (cfg.singlePagePerInterval !== 'no'); - const maxTime = cfg.endTime ? ` AND LastModifiedDate <= ${cfg.endTime}` : ''; - let hasMorePages = true; - let lastSeenTime = snapshot.previousLastModified; - - // the query for the object and all its linked parent objects - const selectedObjects = linkedObjects.reduce((query, obj) => { - if (!obj.startsWith('!')) return `${query}, ${obj}.*`; - return query; - }, '*'); - - // selecting the object and all its parents - let query = conn.sobject(cfg.object) - .select(selectedObjects); - - // the query for all the linked child objects - query = linkedObjects.reduce((newQuery, obj) => { - if (obj.startsWith('!')) { - return newQuery.include(obj.slice(1)) - .select('*') - .end(); - } - return newQuery; - }, query); - - do { - let whereCondition; - if (lastSeenTime === cfg.startTime || lastSeenTime === '1970-01-01T00:00:00.000Z') whereCondition = `LastModifiedDate >= ${lastSeenTime}${maxTime}`; - else whereCondition = `LastModifiedDate > ${lastSeenTime}${maxTime}`; - try { - const results = await query.where(whereCondition) - .sort({ LastModifiedDate: 1 }) - .execute({ autoFetch: true, maxFetch }); - processResults(results); - } catch (err) { - emitError(createPresentableError(err) || err); - emitEnd(); - return; - } - if (singlePagePerInterval) { - hasMorePages = false; - } - } while (hasMorePages); - - if (snapshot.previousLastModified !== lastSeenTime) { - snapshot.previousLastModified = lastSeenTime; - callScope.logger.info('emitting new snapshot: %j', snapshot); - emitSnapshot(snapshot); + throw new Error('Size of Polling Page needs to be positive integer, max 10000 objects'); } - emitEnd(); + } - function processResults(records) { + // the query for the object and all its linked parent objects + const selectedObjects = linkedObjects.reduce((query, obj) => { + if (!obj.startsWith('!')) return `${query}, ${obj}.*`; + return query; + }, '*'); + const queryOptions = { selectedObjects, linkedObjects, maxFetch }; + do { + let whereCondition; + // eslint-disable-next-line max-len + if (lastSeenTime === configuration.startTime || lastSeenTime === '1970-01-01T00:00:00.000Z') whereCondition = `LastModifiedDate >= ${lastSeenTime}${maxTime}`; + else whereCondition = `LastModifiedDate > ${lastSeenTime}${maxTime}`; + try { + queryOptions.whereCondition = whereCondition; + const records = await callJSForceMethod.call(this, configuration, 'pollingSelectQuery', queryOptions); if (!records || !records.length) { - callScope.logger.info('No new objects found'); + this.logger.info('No new objects found'); hasMorePages = false; - return; - } - const { outputMethod = 'emitIndividually' } = cfg; - if (outputMethod === 'emitAll') { - emitData(messages.newMessageWithBody({ records })); - } else if (outputMethod === 'emitIndividually') { - records.forEach((record, i) => { - const newMsg = messages.newEmptyMessage(); - newMsg.headers = { - objectId: record.attributes.url, - }; - newMsg.body = record; - callScope.logger.info('emitting record %d', i); - callScope.logger.debug('emitting record: %j', newMsg); - emitData(newMsg); - }); } else { - throw new Error('Unsupported Output method'); - } - hasMorePages = records.length === maxFetch; - lastSeenTime = records[records.length - 1].LastModifiedDate; - } - }; - - this.processQuery = function processQuery(query, cfg) { - const params = {}; - params.cfg = cfg; - params.cfg.apiVersion = `v${common.globalConsts.SALESFORCE_API_VERSION}`; - - params.query = query; - - Q.ninvoke(self, 'refreshToken', params.cfg) - .then(updateCfg) - .then(paramsUpdated => fetchObjectsQuery(callScope.logger, paramsUpdated)) - .then(processResults) - .fail(onError) - .done(emitEnd); - - function updateCfg(config) { - params.cfg = config; - return params; - } - - function processResults(results) { - if (!results.objects.totalSize) { - callScope.logger.info('No new objects found'); - return; - } - const { records } = results.objects; - const { outputMethod = 'emitIndividually' } = params.cfg; - if (outputMethod === 'emitIndividually') { - records.forEach(emitResultObject); - } else if (outputMethod === 'emitAll') { - emitData(messages.newMessageWithBody({ records })); - } else { - throw new Error('Unsupported Output method'); - } - } - - function emitResultObject(object) { - const msg = messages.newEmptyMessage(); - msg.headers = { - objectId: object.attributes.url, - }; - msg.body = object; - emitData(msg); - } - }; - - /** - * This function will fetch object data from salesforce - * @param conf - * @param next - */ - this.listObjectTypes = function listObjectTypes(conf, next) { - // eslint-disable-next-line consistent-return - self.refreshToken(_.clone(conf), (err, newCfg) => { - if (err) { - return next(err); - } - describe.fetchObjectTypes(callScope.logger, newCfg, next); - }); - }; - - this.listLinkedObjects = function listLinkedObjects(conf, next) { - // eslint-disable-next-line consistent-return - self.refreshToken(_.clone(conf), async (err, newCfg) => { - if (err) { - return next(err); - } - try { - newCfg.sobject = newCfg.object; - const metaLoader = new MetaLoader(newCfg, this); - const relatedObjs = await metaLoader.getLinkedObjectsModel(); - next(null, relatedObjs); - } catch (errInner) { - next(errInner); - } - }); - }; - - /** - * This function modify a scope and add two functions process and getMetaModel - * - * @param objectType - * @param msg - * @param conf - */ - this.processAction = function processAction(objectType, msg, conf) { - // eslint-disable-next-line consistent-return - self.refreshToken(conf, (err, newCfg) => { - if (err) { - return onActionError(err); - } - postData(newCfg, checkPostResult, common.globalConsts.SALESFORCE_API_VERSION); - }); - - function postData(config, next) { - // Here we assume conf has a refreshed Salesforce token - const baseUrl = config.oauth.instance_url; - const authValue = util.format('Bearer %s', config.oauth.access_token); - const url = util.format('%s/services/data/%s/sobjects/%s', baseUrl, `v${common.globalConsts.SALESFORCE_API_VERSION}`, - objectType); - - callScope.logger.debug('getJSON', msg.body); - - httpUtils.getJSON(callScope.logger, { - url, - method: 'POST', - json: msg.body, - auth: authValue, - statusExpected: 201, - }, next); - } - - // eslint-disable-next-line consistent-return - function checkPostResult(err, result) { - if (err) { - return onActionError(err); + const { outputMethod = 'emitIndividually' } = configuration; + if (outputMethod === 'emitAll') { + await this.emit('data', messages.newMessageWithBody({ records })); + } else if (outputMethod === 'emitIndividually') { + for (let i = 0; i < records.length; i += 1) { + const newMsg = messages.newEmptyMessage(); + newMsg.headers = { + objectId: records[i].attributes.url, + }; + newMsg.body = records[i]; + this.logger.debug('emitting record %d', i); + await this.emit('data', newMsg); + } + } else { + throw new Error('Unsupported Output method'); + } + hasMorePages = records.length === maxFetch; + lastSeenTime = records[records.length - 1].LastModifiedDate; } - msg.body = _.extend(msg.body, result); - onActionSuccess(msg); + } catch (err) { + this.logger.error('Error occurred during polling objects'); + throw err; } - - function onActionError(err) { - emitError(err); - emitEnd(); - } - - function onActionSuccess(message) { - emitData(message); - emitEnd(); + if (singlePagePerInterval) { + hasMorePages = false; } - }; + } while (hasMorePages); - function emitError(err) { - callScope.logger.error('emitting SalesforceEntity error', err, err.stack); - callScope.emit('error', err); + if (snapshot.previousLastModified !== lastSeenTime) { + snapshot.previousLastModified = lastSeenTime; + this.logger.trace('emitting new snapshot: %j', snapshot); + await this.emit('snapshot', snapshot); } - - function onError(err) { - emitError(createPresentableError(err) || err); - } - - function emitData(data) { - callScope.emit('data', data); - } - - function emitSnapshot(snapshot) { - callScope.emit('snapshot', snapshot); - } - - function emitKeys(snapshot) { - callScope.emit('updateKeys', snapshot); - } - - function emitEnd() { - callScope.emit('end'); - } -} - -exports.SalesforceEntity = SalesforceEntity; - -exports.buildAction = function buildAction(objectType, exports) { - exports.process = function processAction(msg, conf) { - const self = new SalesforceEntity(this); - self.processAction(objectType, msg, conf, common.globalConsts.SALESFORCE_API_VERSION); - }; - - exports.getMetaModel = function getActionMetaModel(cfg, cb) { - const self = new SalesforceEntity(this); - cfg.object = objectType; - cfg.sobject = cfg.sobject ? cfg.sobject : objectType; - // eslint-disable-next-line consistent-return - self.getInMetaModel(cfg, (err, data) => { - if (err) { - return cb(err); - } - data.out = _.cloneDeep(data.in); - data.out.properties.in = { - type: 'string', - required: true, - }; - cb(null, data); - }); - }; -}; - -exports.buildTrigger = function buildTrigger(objectType, exports) { - exports.process = function processTrigger(msg, conf, snapshot) { - const self = new SalesforceEntity(this); - // Set fixed object type to the configuration object - conf.object = objectType; - // Proceed as usual - self.processTrigger(msg, conf, snapshot); - }; - - exports.getMetaModel = function getTriggerMetaModel(cfg, cb) { - const self = new SalesforceEntity(this); - cfg.object = objectType; - self.getOutMetaModel(cfg, cb); - }; -}; - -exports.objectTypes = function objectTypes(conf, next) { - const self = new SalesforceEntity(this); - self.listObjectTypes(conf, next); -}; - -exports.linkedObjectTypes = function linkedObjectTypes(conf, next) { - const self = new SalesforceEntity(this); - self.listLinkedObjects(conf, next); -}; - -exports.process = function processEntry(msg, conf, snapshot) { - const self = new SalesforceEntity(this); - self.processTrigger(msg, conf, snapshot); -}; - -exports.getMetaModel = function getEntryMetaModel(cfg, cb) { - const self = new SalesforceEntity(this); - self.getOutMetaModel(cfg, cb); + await this.emit('end'); }; diff --git a/lib/helpers/attachment.js b/lib/helpers/attachment.js index a50550a..4980e5b 100644 --- a/lib/helpers/attachment.js +++ b/lib/helpers/attachment.js @@ -5,8 +5,8 @@ const requestPromise = require('request-promise'); const client = require('elasticio-rest-node')(); -const MetaLoader = require('../helpers/metaLoader'); - +const { callJSForceMethod } = require('./wrapper'); +const { getSecret } = require('../util'); async function downloadFile(url, headers) { const optsDownload = { @@ -21,8 +21,7 @@ async function downloadFile(url, headers) { return response.body; } -// eslint-disable-next-line max-len -exports.prepareBinaryData = async function prepareBinaryData(msg, configuration, sfConnection, emitter) { +exports.prepareBinaryData = async function prepareBinaryData(msg, configuration, emitter) { let binField; let attachment; @@ -37,10 +36,9 @@ exports.prepareBinaryData = async function prepareBinaryData(msg, configuration, emitter.logger.info('Found an attachment from the previous component.'); if (configuration.utilizeAttachment) { - const metaLoader = new MetaLoader(configuration, emitter, sfConnection); - const objectFields = await metaLoader.getObjectFieldsMetaData(); + const objectFields = await callJSForceMethod.call(emitter, configuration, 'getObjectFieldsMetaData'); - binField = objectFields.find(field => field.type === 'base64'); + binField = objectFields.find((field) => field.type === 'base64'); if (binField) { emitter.logger.info('Preparing the attachment...'); @@ -49,7 +47,7 @@ exports.prepareBinaryData = async function prepareBinaryData(msg, configuration, // eslint-disable-next-line no-param-reassign msg.body[binField.name] = Buffer.from(data).toString('base64'); - if (attachment['content-type'] && objectFields.find(field => field.name === 'ContentType')) { + if (attachment['content-type'] && objectFields.find((field) => field.name === 'ContentType')) { // eslint-disable-next-line no-param-reassign msg.body.ContentType = attachment['content-type']; } @@ -64,19 +62,18 @@ exports.prepareBinaryData = async function prepareBinaryData(msg, configuration, return binField; }; -// eslint-disable-next-line max-len -exports.getAttachment = async function getAttachment(configuration, objectContent, sfConnection, emitter) { - const metaLoader = new MetaLoader(configuration, emitter, sfConnection); - const objectFields = await metaLoader.getObjectFieldsMetaData(); +exports.getAttachment = async function getAttachment(configuration, objectContent, emitter) { + const objectFields = await callJSForceMethod.call(emitter, configuration, 'getObjectFieldsMetaData'); - const binField = objectFields.find(field => field.type === 'base64'); + const binField = objectFields.find((field) => field.type === 'base64'); if (!binField) return; const binDataUrl = objectContent[binField.name]; if (!binDataUrl) return; - const data = await downloadFile(configuration.oauth.instance_url + binDataUrl, { - Authorization: `Bearer ${sfConnection.accessToken}`, + const { credentials } = await getSecret(emitter, configuration.secretId); + const data = await downloadFile(credentials.undefined_params.instance_url + binDataUrl, { + Authorization: `Bearer ${credentials.accessToken}`, }); const signedUrl = await client.resources.storage.createSignedUrl(); diff --git a/lib/helpers/describe.js b/lib/helpers/describe.js deleted file mode 100644 index 25d3d4a..0000000 --- a/lib/helpers/describe.js +++ /dev/null @@ -1,47 +0,0 @@ -const Q = require('q'); -const util = require('util'); -const httpUtils = require('./http-utils.js'); -const common = require('../common.js'); - -function getObjectDescription(logger, cfg, cb) { - const url = cfg.oauth.instance_url; - const objType = cfg.sobject; - - const metadataURL = util.format('%s/services/data/v%s/sobjects/%s/describe', url, common.globalConsts.SALESFORCE_API_VERSION, objType); - const authValue = util.format('Bearer %s', cfg.oauth.access_token); - - httpUtils.getJSON(logger, { - url: metadataURL, - auth: authValue, - }, cb); -} - -function fetchObjectTypes(logger, conf, cb) { - const url = conf.oauth.instance_url; - - const metadataURL = util.format('%s/services/data/v%s/sobjects', url, common.globalConsts.SALESFORCE_API_VERSION); - const authValue = util.format('Bearer %s', conf.oauth.access_token); - - httpUtils.getJSON(logger, { - url: metadataURL, - auth: authValue, - // eslint-disable-next-line consistent-return - }, (err, data) => { - if (err) { - return cb(err); - } - const result = {}; - data.sobjects.forEach((obj) => { - result[obj.name] = obj.label; - }); - cb(null, result); - }); -} - -function describeObject(logger, params) { - return Q.nfcall(getObjectDescription, logger, params.cfg); -} - -exports.fetchObjectTypes = fetchObjectTypes; -exports.getObjectDescription = getObjectDescription; -exports.describeObject = describeObject; diff --git a/lib/helpers/error.js b/lib/helpers/error.js deleted file mode 100644 index 2667353..0000000 --- a/lib/helpers/error.js +++ /dev/null @@ -1,28 +0,0 @@ -function createPresentableError(err) { - const ERROR_KEY_PREFIX = 'salesforce_'; - const UNKNOWN_ERROR_KEY = `${ERROR_KEY_PREFIX}UNKNOWN`; - let errorBody; - - if (typeof err.responseBody !== 'string') { - return null; - } - - try { - errorBody = JSON.parse(err.responseBody); - if (errorBody.length) { - // eslint-disable-next-line prefer-destructuring - errorBody = errorBody[0]; - } - } catch (parseError) { - return null; - } - - const view = {}; - view.defaultText = errorBody.message || 'An error occured. Please try later'; - view.textKey = errorBody.errorCode ? ERROR_KEY_PREFIX + errorBody.errorCode : UNKNOWN_ERROR_KEY; - // eslint-disable-next-line no-param-reassign - err.view = view; - return err; -} - -module.exports = createPresentableError; diff --git a/lib/helpers/http-utils.js b/lib/helpers/http-utils.js deleted file mode 100644 index b7bdf24..0000000 --- a/lib/helpers/http-utils.js +++ /dev/null @@ -1,96 +0,0 @@ -const util = require('util'); -const request = require('request'); - -/** - * This function creates a header value for Authentication header - * using Basic base64 authentication encoding. - * - * For example username 'foo' and password 'bar' will be transformed into - * - * 'Basic Zm9vOmJhcg==' - * - * @param username - * @param password - * @return {String} - */ -exports.createBasicAuthorization = function createBasicAuthorization(username, password) { - const credentials = util.format('%s:%s', username, password); - return `Basic ${Buffer.from(credentials).toString('base64')}`; -}; - -/** - * This function fetches JSON response and do a necessary parsing and control - * of the exception handling in case unexpected return code is returned - * - * It accept following parameters as properties of the first parameter - * - * url - required url to be fetched - * auth - optional authentication header value - * headers - optional hash with header values, - * please note authentication header will be added automatically as well as Accept header - * - * @param logger - * @param params - * @param cb - */ -exports.getJSON = function getJSON(logger, params, cb) { - const { url } = params; - const method = params.method || 'get'; - const headers = params.headers || {}; - const expectedStatus = params.statusExpected || 200; - - if (params.auth) { - headers.Authorization = params.auth; - } - - logger.trace('Sending %s request to %s', method, url); - - request[method.toLowerCase()]({ - url, - agent: false, - headers, - form: params.form, - json: params.json, - // eslint-disable-next-line consistent-return - }, (err, resp, body) => { - if (err) { - logger.error(`Failed to fetch JSON from ${url} with error: ${err}`); - return cb(err); - } - if (resp.statusCode === expectedStatus) { - let result = body; - try { - if (typeof body === 'string') { - result = JSON.parse(body); - } - } catch (parseError) { - logger.error('Failed to parse JSON', body); - cb(parseError); - } - if (result) { - try { - logger.trace('Have got %d response from %s to %s', expectedStatus, method, url); - cb(null, result); - } catch (e) { - logger.error('Exception happened when passing data down the chain', e); - } - } else { - logger.info('Have got empty response'); - cb(null, result); - } - } else { - const msg = util.format( - 'Unexpected return code %d, expected %d, body %j', - resp.statusCode, - expectedStatus, - body, - ); - logger.error(msg); - - const errorResponse = new Error(msg); - errorResponse.responseBody = body; - errorResponse.statusCode = resp.statusCode; - cb(errorResponse); - } - }); -}; diff --git a/lib/helpers/lookupCache.js b/lib/helpers/lookupCache.js index 227e78f..a11d9e4 100644 --- a/lib/helpers/lookupCache.js +++ b/lib/helpers/lookupCache.js @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-nested-ternary +// eslint-disable-next-line no-nested-ternary,max-classes-per-file const cacheExpirationTime = process.env.NODE_ENV === 'test' ? 1000 : process.env.HASH_LIMIT_TIME ? parseInt(process.env.HASH_LIMIT_TIME, 10) : 600 * 1000; diff --git a/lib/helpers/metaLoader.js b/lib/helpers/metaLoader.js deleted file mode 100644 index 0c21524..0000000 --- a/lib/helpers/metaLoader.js +++ /dev/null @@ -1,282 +0,0 @@ -const sfConnection = require('./sfConnection.js'); - -const TYPES_MAP = { - address: 'address', - anyType: 'string', - base64: 'string', - boolean: 'boolean', - byte: 'string', - calculated: 'string', - combobox: 'string', - currency: 'number', - DataCategoryGroupReference: 'string', - date: 'string', - datetime: 'string', - double: 'number', - encryptedstring: 'string', - email: 'string', - id: 'string', - int: 'number', - JunctionIdList: 'JunctionIdList', - location: 'location', - masterrecord: 'string', - multipicklist: 'multipicklist', - percent: 'double', - phone: 'string', - picklist: 'string', - reference: 'string', - string: 'string', - textarea: 'string', - time: 'string', - url: 'string', -}; - -module.exports = class MetaLoader { - constructor(configuration, emitter, connection) { - this.configuration = configuration; - this.emitter = emitter; - if (connection) { - this.connection = connection; - } else { - this.connection = sfConnection.createConnection(configuration, emitter); - } - } - - getObjectMetaData() { - return this.connection.describe(this.configuration.sobject); - } - - getObjectFieldsMetaData() { - return this.getObjectMetaData().then(meta => meta.fields); - } - - async ObjectMetaData() { - const objMetaData = await this.getObjectMetaData(); - - return { - findFieldByLabel: function findFieldByLabel(fieldLabel) { - return objMetaData.fields.find(field => field.label === fieldLabel); - }, - isStringField: function isStringField(field) { - return field && (field.soapType === 'tns:ID' || field.soapType === 'xsd:string'); - }, - }; - } - - async getLookupFieldsModel() { - return this.connection.describe(this.configuration.sobject) - .then(async (meta) => { - const model = {}; - await meta.fields - .filter(field => field.externalId || field.unique || field.name === 'Id' || field.type === 'reference') - .forEach((field) => { - model[field.name] = field.label; - }); - return model; - }); - } - - async getLookupFieldsModelWithTypeOfSearch(typeOfSearch) { - if (typeOfSearch === 'uniqueFields') { - return this.connection.describe(this.configuration.sobject) - .then(async (meta) => { - const model = {}; - await meta.fields - .filter(field => field.type === 'id' || field.unique) - .forEach((field) => { - model[field.name] = `${field.label} (${field.name})`; - }); - return model; - }); - } - return this.connection.describe(this.configuration.sobject) - .then(async (meta) => { - const model = {}; - await meta.fields - .forEach((field) => { - model[field.name] = `${field.label} (${field.name})`; - }); - return model; - }); - } - - async getLinkedObjectsModel() { - const meta = await this.connection.describe(this.configuration.sobject); - return { - ...meta.fields.filter(field => field.type === 'reference') - .reduce((obj, field) => { - if (!field.referenceTo.length) { - throw new Error( - `Empty referenceTo array for field of type 'reference' with name ${field.name} field=${JSON.stringify( - field, null, ' ', - )}`, - ); - } - if (field.relationshipName !== null) { - // eslint-disable-next-line no-param-reassign - obj[field.relationshipName] = `${field.referenceTo.join(', ')} (${field.relationshipName})`; - } - return obj; - }, {}), - ...meta.childRelationships - .reduce((obj, child) => { - if (child.relationshipName) { - // add a '!' flag to distinguish between child and parent relationships, - // will be popped off in lookupObject.processAction - - // eslint-disable-next-line no-param-reassign - obj[`!${child.relationshipName}`] = `${child.childSObject} (${child.relationshipName})`; - } - return obj; - }, {}), - }; - } - - async loadMetadata() { - return this.connection.describe(this.configuration.sobject) - .then(async meta => this.processMeta(meta)).then((metaData) => { - this.emitter.logger.debug('emitting Metadata %j', metaData); - return metaData; - }); - } - - async loadSOQLRequest() { - return this.connection.describe(this.configuration.sobject) - .then(meta => `SELECT ${meta.fields.map(field => field.name).join(',')} FROM ${this.configuration.sobject}`); - } - - async processMeta(meta) { - const result = { - in: { - type: 'object', - }, - out: { - type: 'object', - }, - }; - result.in.properties = {}; - result.out.properties = {}; - const inProp = result.in.properties; - const outProp = result.out.properties; - let fields = await meta.fields.filter(field => !field.deprecatedAndHidden); - - if (this.configuration.metaType !== 'lookup') { - fields = await fields.filter(field => field.updateable && field.createable); - } - await fields.forEach((field) => { - if (this.configuration.metaType === 'lookup' && field.name === this.configuration.lookupField) { - inProp[field.name] = this.createProperty(field); - } else if (this.configuration.metaType !== 'lookup' && field.createable) { - inProp[field.name] = this.createProperty(field); - } - outProp[field.name] = this.createProperty(field); - }); - if (this.configuration.metaType === 'upsert') { - Object.keys(inProp).forEach((key) => { - inProp[key].required = false; - }); - inProp.Id = { - type: 'string', - required: false, - title: 'Id', - }; - } - return result; - } - - /** - * This method returns a property description for e.io proprietary schema - * - * @param field - */ - // eslint-disable-next-line class-methods-use-this - createProperty(field) { - let result = {}; - result.type = TYPES_MAP[field.type]; - if (!result.type) { - throw new Error( - `Can't convert type for type=${field.type} field=${JSON.stringify( - field, null, ' ', - )}`, - ); - } - if (field.type === 'textarea') { - result.maxLength = 1000; - } else if (field.type === 'picklist') { - result.enum = field.picklistValues.filter(p => p.active) - .map(p => p.value); - } else if (field.type === 'multipicklist') { - result = { - type: 'array', - items: { - type: 'string', - enum: field.picklistValues.filter(p => p.active) - .map(p => p.value), - }, - }; - } else if (field.type === 'JunctionIdList') { - result = { - type: 'array', - items: { - type: 'string', - }, - }; - } else if (field.type === 'address') { - result.type = 'object'; - result.properties = { - city: { type: 'string' }, - country: { type: 'string' }, - postalCode: { type: 'string' }, - state: { type: 'string' }, - street: { type: 'string' }, - }; - } else if (field.type === 'location') { - result.type = 'object'; - result.properties = { - latitude: { type: 'string' }, - longitude: { type: 'string' }, - }; - } - result.required = !field.nillable && !field.defaultedOnCreate; - result.title = field.label; - result.default = field.defaultValue; - return result; - } - - getSObjectList(what, filter) { - this.emitter.logger.info(`Fetching ${what} list...`); - return this.connection.describeGlobal().then((response) => { - const result = {}; - response.sobjects.forEach((object) => { - if (filter(object)) { - result[object.name] = object.label; - } - }); - this.emitter.logger.info('Found %s sobjects', Object.keys(result).length); - this.emitter.logger.debug('Found sobjects: %j', result); - return result; - }); - } - - getCreateableObjectTypes() { - return this.getSObjectList('createable sobject', object => object.createable); - } - - getUpdateableObjectTypes() { - return this.getSObjectList('updateable sobject', object => object.updateable); - } - - getObjectTypes() { - return this.getSObjectList('updateable/createable sobject', object => object.updateable && object.createable); - } - - getSearchableObjectTypes() { - return this.getSObjectList('searchable sobject', object => object.queryable); - } - - getPlatformEvents() { - return this.getSObjectList('event sobject', object => object.name.endsWith('__e')); - } -}; - -module.exports.TYPES_MAP = TYPES_MAP; diff --git a/lib/helpers/metadata.js b/lib/helpers/metadata.js deleted file mode 100644 index 34d7886..0000000 --- a/lib/helpers/metadata.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Utility functions that transform Salesforce metadata to JSON Schema - * */ -const _ = require('lodash'); - -const FIELD_TYPE_TO_SCHEMA_TYPE = { - 'tns:ID': 'string', - 'xsd:boolean': 'boolean', - 'xsd:string': 'string', - 'xsd:dateTime': 'string', - 'xsd:double': 'number', - 'xsd:int': 'integer', - 'xsd:date': 'string', - 'xsd:time': 'string', - 'xsd:base64Binary': 'string', -}; - -// eslint-disable-next-line no-underscore-dangle -function _addEnum(field, result) { - // eslint-disable-next-line no-param-reassign - result.enum = []; - const array = result.enum; - _.each(field.picklistValues, (alternative) => { - array.push(alternative.value); - }); -} - -// eslint-disable-next-line no-underscore-dangle -function _fieldToProperty(field) { - const type = FIELD_TYPE_TO_SCHEMA_TYPE[field.soapType]; - if (!type) { - throw new Error(`Can't convert salesforce soapType ${field.soapType - } to JSON schema type`); - } - const result = { - type: FIELD_TYPE_TO_SCHEMA_TYPE[field.soapType], - title: field.label, - default: field.defaultValue, - required: !field.nillable && !field.defaultedOnCreate, - custom: field.custom, - readonly: field.calculated || !field.updateable, - }; - if (field.type === 'picklist') { - _addEnum(field, result); - } - return result; -} - -/** - * We will filter out properties such as: - * - Deprecated and Hidden - * - If referenceTo is set - * - If relationshipName is set - * - Not creatable and not updatable - * - * @param field - * @param metaType (in or out structure) - * @returns {*} - * @private - */ -// eslint-disable-next-line no-underscore-dangle -function _filterProperties(field, metaType) { - if (field.name === 'ExtId__c') { - return true; - } - if (field.deprecatedAndHidden) { - return false; - } - - if (!field.updateable && !field.createable) { - if (metaType !== 'out') { - return false; - } - } - return true; -} - -function buildSchemaFromDescription(objectDescription, metaType) { - const result = { - description: objectDescription.name, - type: 'object', - properties: {}, - }; - // eslint-disable-next-line max-len - const filtered = _.filter(objectDescription.fields, objDesc => _filterProperties(objDesc, metaType)); - _.each(filtered, (field) => { - const { name } = field; - result.properties[name] = _fieldToProperty(field); - /** When creating an object in Salesforce the field `ownerID` should be optional - * https://github.com/elasticio/salesforce-component/issues/26 - * */ - if (name === 'OwnerId') { - result.properties[name].required = false; - } - }); - return result; -} - -function pickSelectFields(metadata) { - if (!metadata || !metadata.properties || _.isEqual({}, metadata.properties)) { - throw new Error('No out metadata found to create select fields from'); - } - - return _.keys(metadata.properties).join(','); -} - -/** - * Exported converter function - * - * @param source - * @return {Object} - */ -exports.buildSchemaFromDescription = buildSchemaFromDescription; -exports.pickSelectFields = pickSelectFields; diff --git a/lib/helpers/oauth-utils.js b/lib/helpers/oauth-utils.js deleted file mode 100644 index e0db255..0000000 --- a/lib/helpers/oauth-utils.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Common functionality for OAuth - * */ -const util = require('util'); -const { handlebars } = require('hbs'); -const _ = require('lodash'); -const httpUtils = require('./http-utils.js'); - -const appDef = require('../../component.json'); - -function getValueFromEnv(key) { - const compiled = handlebars.compile(key); - const value = compiled(process.env); - if (value) { - return value; - } - throw new Error(util.format("No value is defined for environment variable: '%s'", key)); -} - -/** - * This function resolves the variables in the string using hanlebars - * - * @param template - * @param context - * @returns {*} - */ -function resolveVars(template, context) { - const compiled = handlebars.compile(template); - return compiled(context); -} - -/** - * This function refreshes OAuth token. - * - * @param logger - * @param serviceUri - * @param clientIdKey - * @param clientSecretKey - * @param conf - * @param next - */ -function refreshToken(logger, serviceUri, clientIdKey, clientSecretKey, conf, next) { - const clientId = getValueFromEnv(clientIdKey); - const clientSecret = getValueFromEnv(clientSecretKey); - - // Now we need to resolve URI in case we have a replacement groups inside it - // for example for Salesforce we have a production and test environemnt - // or shopware the user domain is part of OAuth URIs - const refreshURI = resolveVars(serviceUri, conf); - - const params = { - grant_type: 'refresh_token', - client_id: clientId, - client_secret: clientSecret, - refresh_token: conf.oauth ? conf.oauth.refresh_token : null, - format: 'json', - }; - - const newConf = _.cloneDeep(conf); - - httpUtils.getJSON(logger, { - url: refreshURI, - method: 'post', - form: params, - // eslint-disable-next-line consistent-return - }, (err, refreshResponse) => { - if (err) { - logger.error('Failed to refresh token from %s', serviceUri); - return next(err); - } - logger.info('Refreshed token from %s', serviceUri); - // update access token in configuration - newConf.oauth.access_token = refreshResponse.access_token; - // if new refresh_token returned, update that also - // specification is here http://tools.ietf.org/html/rfc6749#page-47 - if (refreshResponse.refresh_token) { - newConf.oauth.refresh_token = refreshResponse.refresh_token; - } - next(null, newConf); - }); -} - -function refreshAppToken(logger, app, conf, cb) { - const credentials = appDef.credentials || {}; - const { oauth2 } = credentials; - - refreshToken( - logger, - oauth2.token_uri, - oauth2.client_id, - oauth2.client_secret, - conf, - cb, - ); -} - -exports.refreshToken = refreshToken; -exports.refreshAppToken = refreshAppToken; diff --git a/lib/helpers/objectFetcher.js b/lib/helpers/objectFetcher.js deleted file mode 100644 index 4865074..0000000 --- a/lib/helpers/objectFetcher.js +++ /dev/null @@ -1,47 +0,0 @@ -const Q = require('q'); -const util = require('util'); -const url = require('url'); -const httpUtils = require('./http-utils.js'); - -function fetchObjects(logger, params) { - if (!params.cfg) { - throw new Error('Can\'t fetch objects without a configuration parameter'); - } - if (!params.cfg.apiVersion) { - throw new Error('Can\'t fetch objects without an apiVersion'); - } - if (!params.cfg.object) { - throw new Error('Can\'t fetch objects without an object type'); - } - if (!params.snapshot) { - throw new Error('Can\'t fetch objects without a predefined snapshot'); - } - - const { cfg } = params; - const objType = cfg.object; - const { selectFields } = params; - const { snapshot } = params; - const baseUrl = cfg.oauth.instance_url; - const version = cfg.apiVersion; - - const queryUrl = util.format('%s/services/data/%s/query', baseUrl, version); - const query = util.format('select %s from %s where SystemModstamp > %s', selectFields, objType, snapshot); - const queryString = url.format({ - query: { - q: query, - }, - }); - - const authValue = util.format('Bearer %s', cfg.oauth.access_token); - - return Q.ninvoke(httpUtils, 'getJSON', logger, { - url: queryUrl + queryString, - auth: authValue, - }).then((objects) => { - // eslint-disable-next-line no-param-reassign - params.objects = objects; - return params; - }); -} - -module.exports = fetchObjects; diff --git a/lib/helpers/objectFetcherQuery.js b/lib/helpers/objectFetcherQuery.js deleted file mode 100644 index 45ccc6f..0000000 --- a/lib/helpers/objectFetcherQuery.js +++ /dev/null @@ -1,41 +0,0 @@ -const Q = require('q'); -const util = require('util'); -const url = require('url'); -const httpUtils = require('./http-utils.js'); - -function fetchObjects(logger, params) { - if (!params.cfg) { - throw new Error('Can\'t fetch objects without a configuration parameter'); - } - if (!params.cfg.apiVersion) { - throw new Error('Can\'t fetch objects without an apiVersion'); - } - if (!params.query) { - throw new Error('Can\'t fetch objects without a query'); - } - const { cfg } = params; - const baseUrl = cfg.oauth.instance_url; - const version = cfg.apiVersion; - - const queryUrl = util.format('%s/services/data/%s/query', baseUrl, version); - const queryString = url.format({ - query: { - q: params.query, - }, - }); - - logger.info('executing query:', params.query); - - const authValue = util.format('Bearer %s', cfg.oauth.access_token); - - return Q.ninvoke(httpUtils, 'getJSON', logger, { - url: queryUrl + queryString, - auth: authValue, - }).then((objects) => { - // eslint-disable-next-line no-param-reassign - params.objects = objects; - return params; - }); -} - -module.exports = fetchObjects; diff --git a/lib/helpers/sfConnection.js b/lib/helpers/sfConnection.js deleted file mode 100644 index 3864308..0000000 --- a/lib/helpers/sfConnection.js +++ /dev/null @@ -1,25 +0,0 @@ -const jsforce = require('jsforce'); -const common = require('../common.js'); - - -exports.createConnection = function createConnection(configuration, emitter) { - const connection = new jsforce.Connection({ - oauth2: { - clientId: process.env.OAUTH_CLIENT_ID, - clientSecret: process.env.OAUTH_CLIENT_SECRET, - }, - instanceUrl: configuration.oauth.instance_url, - accessToken: configuration.oauth.access_token, - refreshToken: configuration.oauth.refresh_token, - version: common.globalConsts.SALESFORCE_API_VERSION, - }); - - connection.on('refresh', (accessToken, res) => { - emitter.logger.debug('Keys were updated, res=%j', res); - emitter.emit('updateKeys', { oauth: res }); - }); - - connection.on('error', err => emitter.emit('error', err)); - - return connection; -}; diff --git a/lib/helpers/utils.js b/lib/helpers/utils.js new file mode 100644 index 0000000..35987d7 --- /dev/null +++ b/lib/helpers/utils.js @@ -0,0 +1,192 @@ +const TYPES_MAP = { + address: 'address', + anyType: 'string', + base64: 'string', + boolean: 'boolean', + byte: 'string', + calculated: 'string', + combobox: 'string', + currency: 'number', + DataCategoryGroupReference: 'string', + date: 'string', + datetime: 'string', + double: 'number', + encryptedstring: 'string', + email: 'string', + id: 'string', + int: 'number', + JunctionIdList: 'JunctionIdList', + location: 'location', + masterrecord: 'string', + multipicklist: 'multipicklist', + percent: 'double', + phone: 'string', + picklist: 'string', + reference: 'string', + string: 'string', + textarea: 'string', + time: 'string', + url: 'string', +}; + +const META_TYPES_MAP = { + lookup: 'lookup', + upsert: 'upsert', + create: 'create', + polling: 'polling', +}; + +/** + * This method returns a property description for e.io proprietary schema + * + * @param field + */ +function createProperty(field) { + let result = {}; + result.type = TYPES_MAP[field.type]; + if (!result.type) { + throw new Error( + `Can't convert type for type=${field.type} field=${JSON.stringify( + field, null, ' ', + )}`, + ); + } + if (field.type === 'textarea') { + result.maxLength = 1000; + } else if (field.type === 'picklist') { + result.enum = field.picklistValues.filter((p) => p.active) + .map((p) => p.value); + } else if (field.type === 'multipicklist') { + result = { + type: 'array', + items: { + type: 'string', + enum: field.picklistValues.filter((p) => p.active) + .map((p) => p.value), + }, + }; + } else if (field.type === 'JunctionIdList') { + result = { + type: 'array', + items: { + type: 'string', + }, + }; + } else if (field.type === 'address') { + result.type = 'object'; + result.properties = { + city: { type: 'string' }, + country: { type: 'string' }, + postalCode: { type: 'string' }, + state: { type: 'string' }, + street: { type: 'string' }, + }; + } else if (field.type === 'location') { + result.type = 'object'; + result.properties = { + latitude: { type: 'string' }, + longitude: { type: 'string' }, + }; + } + result.required = !field.nillable && !field.defaultedOnCreate; + result.title = field.label; + result.default = field.defaultValue; + return result; +} + +exports.processMeta = async function processMeta(meta, metaType, lookupField) { + const result = { + in: { + type: 'object', + }, + out: { + type: 'object', + }, + }; + result.in.properties = {}; + result.out.properties = {}; + const inProp = result.in.properties; + const outProp = result.out.properties; + let fields = await meta.fields.filter((field) => !field.deprecatedAndHidden); + + if (metaType === META_TYPES_MAP.create || metaType === META_TYPES_MAP.upsert) { + fields = await fields.filter((field) => field.updateable && field.createable); + } + await fields.forEach((field) => { + if (metaType === META_TYPES_MAP.lookup && field.name === lookupField) { + inProp[field.name] = createProperty(field); + } else if (metaType !== META_TYPES_MAP.lookup && field.createable) { + inProp[field.name] = createProperty(field); + } + outProp[field.name] = createProperty(field); + }); + if (metaType === META_TYPES_MAP.upsert) { + Object.keys(inProp).forEach((key) => { + inProp[key].required = false; + }); + inProp.Id = { + type: 'string', + required: false, + title: 'Id', + }; + } + if (metaType === META_TYPES_MAP.create) { + outProp.id = { + type: 'string', + required: true, + }; + } + return result; +}; + +exports.getLookupFieldsModelWithTypeOfSearch = async function getLookupFieldsModelWithTypeOfSearch(meta, typeOfSearch) { + const model = {}; + if (typeOfSearch === 'uniqueFields') { + await meta.fields + .filter((field) => field.type === 'id' || field.unique) + .forEach((field) => { + model[field.name] = `${field.label} (${field.name})`; + }); + } else { + await meta.fields + .forEach((field) => { + model[field.name] = `${field.label} (${field.name})`; + }); + } + return model; +}; + +exports.getLinkedObjectTypes = function getLinkedObjectTypes(meta) { + const referenceFields = meta.fields.filter((field) => field.type === 'reference') + .reduce((obj, field) => { + if (!field.referenceTo.length) { + throw new Error( + `Empty referenceTo array for field of type 'reference' with name ${field.name} field=${JSON.stringify( + field, null, ' ', + )}`, + ); + } + if (field.relationshipName !== null) { + // eslint-disable-next-line no-param-reassign + obj[field.relationshipName] = `${field.referenceTo.join(', ')} (${field.relationshipName})`; + } + return obj; + }, {}); + const childRelationships = meta.childRelationships + .reduce((obj, child) => { + if (child.relationshipName) { + // add a '!' flag to distinguish between child and parent relationships, + // will be popped off in lookupObject.processAction + + // eslint-disable-next-line no-param-reassign + obj[`!${child.relationshipName}`] = `${child.childSObject} (${child.relationshipName})`; + } + return obj; + }, {}); + return { + ...referenceFields, ...childRelationships, + }; +}; + +module.exports.TYPES_MAP = TYPES_MAP; +module.exports.createProperty = createProperty; diff --git a/lib/helpers/wrapper.js b/lib/helpers/wrapper.js new file mode 100644 index 0000000..d8667f4 --- /dev/null +++ b/lib/helpers/wrapper.js @@ -0,0 +1,66 @@ +/* eslint-disable no-await-in-loop */ +const { SalesForceClient } = require('../salesForceClient'); +const { getSecret, refreshToken } = require('../util'); +const { REFRESH_TOKEN_RETRIES } = require('../common.js').globalConsts; + +let client; + +exports.callJSForceMethod = async function callJSForceMethod(configuration, method, options) { + this.logger.debug('Preparing SalesForce Client...'); + let accessToken; + let instanceUrl; + const { secretId } = configuration; + if (secretId) { + this.logger.debug('Fetching credentials by secretId'); + const { credentials } = await getSecret(this, secretId); + accessToken = credentials.access_token; + instanceUrl = credentials.undefined_params.instance_url; + } else { + this.logger.debug('Fetching credentials from configuration'); + accessToken = configuration.oauth.access_token; + instanceUrl = configuration.oauth.undefined_params.instance_url; + } + let result; + let isSuccess = false; + let iteration = REFRESH_TOKEN_RETRIES; + do { + iteration -= 1; + try { + this.logger.debug('Iteration: %s', REFRESH_TOKEN_RETRIES - iteration); + this.logger.trace('AccessToken = %s, Iteration: %s', accessToken, REFRESH_TOKEN_RETRIES - iteration); + const cfg = { + ...configuration, + access_token: accessToken, + instance_url: instanceUrl, + }; + if (!client || Object.entries(client.configuration).toString() !== Object.entries(cfg).toString()) { + this.logger.debug('Try to create SalesForce Client', REFRESH_TOKEN_RETRIES - iteration); + client = new SalesForceClient(this, cfg); + this.logger.debug('SalesForce Client is created'); + } + this.logger.debug('Trying to call method %s', method); + result = await client[method](options); + isSuccess = true; + this.logger.debug('Method %s was successfully executed', method); + break; + } catch (e) { + this.logger.error('Got error: ', e); + if (e.name === 'INVALID_SESSION_ID') { + try { + this.logger.debug('Session is expired, trying to refresh token...'); + accessToken = await refreshToken(this, secretId); + this.logger.debug('Token is successfully refreshed'); + client = undefined; + } catch (err) { + this.logger.error(err, 'Failed to refresh token'); + } + } else { + throw e; + } + } + } while (iteration > 0); + if (!isSuccess) { + throw new Error('Failed to fetch and/or refresh token, retries exceeded'); + } + return result; +}; diff --git a/lib/salesForceClient.js b/lib/salesForceClient.js new file mode 100644 index 0000000..6f91c66 --- /dev/null +++ b/lib/salesForceClient.js @@ -0,0 +1,277 @@ +const jsforce = require('jsforce'); +const common = require('./common.js'); + +class SalesForceClient { + constructor(context, configuration) { + this.logger = context.logger; + this.configuration = configuration; + this.connection = new jsforce.Connection({ + instanceUrl: configuration.instance_url, + accessToken: configuration.access_token, + version: common.globalConsts.SALESFORCE_API_VERSION, + }); + } + + async describeGlobal() { + return this.connection.describeGlobal(); + } + + async describe(sobject) { + return this.connection.describe(sobject || this.configuration.sobject); + } + + async getObjectFieldsMetaData() { + return this.describe().then((meta) => meta.fields); + } + + async getSObjectList(what, filter) { + this.logger.debug(`Fetching ${what} list...`); + const response = await this.describeGlobal(); + const result = {}; + response.sobjects.forEach((object) => { + if (filter(object)) { + result[object.name] = object.label; + } + }); + this.logger.debug('Found %s sobjects', Object.keys(result).length); + return result; + } + + async getObjectTypes() { + return this.getSObjectList('updateable/createable sobject', (object) => object.updateable && object.createable); + } + + async getCreateableObjectTypes() { + return this.getSObjectList('createable sobject', (object) => object.createable); + } + + async getUpdateableObjectTypes() { + return this.getSObjectList('updateable sobject', (object) => object.updateable); + } + + async getSearchableObjectTypes() { + return this.getSObjectList('searchable sobject', (object) => object.queryable); + } + + async getPlatformEvents() { + return this.getSObjectList('event sobject', (object) => object.name.endsWith('__e')); + } + + async queryEmitAll(query) { + const result = []; + await new Promise((resolve, reject) => { + const response = this.connection.query(query) + .scanAll(this.configuration.includeDeleted) + .on('record', (record) => { + result.push(record); + }) + .on('end', () => { + this.logger.debug('Query executed, total in database=%s, total fetched=%s', response.totalSize, response.totalFetched); + resolve(); + }) + .on('error', (err) => { + reject(err); + }); + }); + return result; + } + + async queryEmitBatch(query) { + let batch = []; + const results = []; + const maxFetch = this.configuration.maxFetch || 1000; + await new Promise((resolve, reject) => { + const response = this.connection.query(query) + .scanAll(this.configuration.includeDeleted) + .on('record', (record) => { + batch.push(record); + if (batch.length >= this.configuration.batchSize) { + this.logger.debug('Ready batch'); + results.push(batch); + batch = []; + } + }) + .on('end', () => { + if (batch.length > 0) { + this.logger.debug('Pushing last batch'); + results.push(batch); + } + this.logger.debug('Query executed, total in database=%s, total fetched=%s', response.totalSize, response.totalFetched); + resolve(); + }) + .on('error', (err) => { + reject(err); + }) + .run({ autoFetch: true, maxFetch }); + }); + return results; + } + + async queryEmitIndividually(query) { + const results = []; + const maxFetch = this.configuration.maxFetch || 1000; + await new Promise((resolve, reject) => { + const response = this.connection.query(query) + .scanAll(this.configuration.includeDeleted) + .on('record', (record) => { + results.push(record); + }) + .on('end', () => { + this.logger.debug('Query executed, total in database=%s, total fetched=%s', response.totalSize, response.totalFetched); + resolve(); + }) + .on('error', (err) => { + reject(err); + }) + .run({ autoFetch: true, maxFetch }); + }); + return results; + } + + async sobjectCreate(options) { + const sobject = options.sobject || this.configuration.sobject; + const { body } = options; + return this.connection.sobject(sobject).create(body); + } + + async sobjectDelete(options) { + const sobject = options.sobject || this.configuration.sobject; + const { id } = options; + return this.connection.sobject(sobject).delete(id); + } + + async sobjectUpdate(options) { + const sobject = options.sobject || this.configuration.sobject; + const { body } = options; + return this.connection.sobject(sobject).update(body); + } + + async sobjectUpsert(options) { + const sobject = options.sobject || this.configuration.sobject; + const extIdField = options.extIdField || this.configuration.extIdField; + const { body } = options; + return this.connection.sobject(sobject).upsert(body, extIdField); + } + + async sobjectLookup(options) { + const sobject = options.sobject || this.configuration.sobject; + const lookupField = options.lookupField || this.configuration.lookupField; + const { body } = options; + const maxFetch = this.configuration.maxFetch || 1000; + const results = []; + await this.connection.sobject(sobject) + .select('*') + .where(`${lookupField} = '${body[lookupField]}'`) + .on('record', (record) => { + results.push(record); + }) + .on('end', () => { + this.logger.debug('Found %s records', results.length); + }) + .on('error', async (err) => { + await this.emit('error', err); + }) + .execute({ autoFetch: true, maxFetch }); + return results; + } + + async selectQuery(options) { + const sobject = options.sobject || this.configuration.sobject; + const results = []; + const { condition } = options; + await this.connection.sobject(sobject) + .select('*') + .where(condition) + .on('record', (record) => { + results.push(record); + }) + .on('end', () => { + this.logger.debug('Found %s records', results.length); + }) + .on('error', (err) => { + throw err; + }) + .execute({ autoFetch: true, maxFetch: 2 }); + return results; + } + + async pollingSelectQuery(options) { + const sobject = options.sobject || this.configuration.sobject; + const { + selectedObjects, linkedObjects, whereCondition, maxFetch, + } = options; + let query = this.connection.sobject(sobject) + .select(selectedObjects); + + // the query for all the linked child objects + query = linkedObjects.reduce((newQuery, obj) => { + if (obj.startsWith('!')) { + return newQuery.include(obj.slice(1)) + .select('*') + .end(); + } + return newQuery; + }, query); + return query.where(whereCondition) + .sort({ LastModifiedDate: 1 }) + .execute({ autoFetch: true, maxFetch }); + } + + async lookupQuery(options) { + const records = []; + const sobject = options.sobject || this.configuration.sobject; + const includeDeleted = options.includeDeleted || this.configuration.includeDeleted; + const { wherePart, offset, limit } = options; + try { + await this.connection.sobject(sobject) + .select('*') + .where(wherePart) + .offset(offset) + .limit(limit) + .scanAll(includeDeleted) + .on('error', (err) => { + this.logger.error('Salesforce returned an error'); + throw err; + }) + .on('record', (record) => { + records.push(record); + }) + .on('end', () => { + this.logger.debug('Found %s records', records.length); + }) + .execute({ autoFetch: true, maxFetch: limit }); + } catch (e) { + this.logger.trace('Lookup query failed', e); + this.logger.error('Lookup query failed'); + throw e; + } + return records; + } + + async bulkQuery(query) { + return this.connection.bulk.query(query) + .on('error', (err) => { + throw err; + }) + .stream(); + } + + async bulkCreateJob(options) { + const { sobject, operation, extra } = options; + return this.connection.bulk.createJob(sobject, operation, extra); + } + + async objectMetaData() { + const objMetaData = await this.describe(); + + return { + findFieldByLabel: function findFieldByLabel(fieldLabel) { + return objMetaData.fields.find((field) => field.label === fieldLabel); + }, + isStringField: function isStringField(field) { + return field && (field.soapType === 'tns:ID' || field.soapType === 'xsd:string'); + }, + }; + } +} +module.exports.SalesForceClient = SalesForceClient; diff --git a/lib/triggers/account.js b/lib/triggers/account.js deleted file mode 100644 index 2675772..0000000 --- a/lib/triggers/account.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildTrigger('Account', exports, '25.0'); diff --git a/lib/triggers/case.js b/lib/triggers/case.js deleted file mode 100644 index 191cc55..0000000 --- a/lib/triggers/case.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildTrigger('Case', exports, '25.0'); diff --git a/lib/triggers/contact.js b/lib/triggers/contact.js deleted file mode 100644 index 08573de..0000000 --- a/lib/triggers/contact.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildTrigger('Contact', exports, '25.0'); diff --git a/lib/triggers/lead.js b/lib/triggers/lead.js deleted file mode 100644 index 750e1d8..0000000 --- a/lib/triggers/lead.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildTrigger('Lead', exports, '25.0'); diff --git a/lib/triggers/query.js b/lib/triggers/query.js index 8721784..18d9c3c 100644 --- a/lib/triggers/query.js +++ b/lib/triggers/query.js @@ -1,8 +1,23 @@ +const { messages } = require('elasticio-node'); +const { callJSForceMethod } = require('../helpers/wrapper'); -const { SalesforceEntity } = require('../entry.js'); - -exports.process = function processTrigger(msg, conf) { - const entity = new SalesforceEntity(this); - const { query } = conf; - return entity.processQuery(query, conf); +exports.process = async function processTrigger(msg, configuration) { + this.logger.info('Starting Query trigger'); + const { query, outputMethod = 'emitIndividually' } = configuration; + const records = await callJSForceMethod.call(this, configuration, 'queryEmitAll', query); + if (records.length === 0) { + await this.emit('data', messages.newEmptyMessage()); + } else if (outputMethod === 'emitAll') { + this.logger.debug('Selected Output method Emit all'); + await this.emit('data', messages.newMessageWithBody({ records })); + } else if (outputMethod === 'emitIndividually') { + this.logger.debug('Selected Output method Emit individually'); + // eslint-disable-next-line no-restricted-syntax + for (const record of records) { + // eslint-disable-next-line no-await-in-loop + await this.emit('data', messages.newMessageWithBody(record)); + } + } else { + throw new Error('Unsupported Output method'); + } }; diff --git a/lib/triggers/streamPlatformEvents.js b/lib/triggers/streamPlatformEvents.js index 5d1c0f0..4326262 100644 --- a/lib/triggers/streamPlatformEvents.js +++ b/lib/triggers/streamPlatformEvents.js @@ -1,69 +1,77 @@ -const elasticio = require('elasticio-node'); - -const { messages } = elasticio; const jsforce = require('jsforce'); -const MetaLoader = require('../helpers/metaLoader'); -const common = require('../common.js'); - -let conn; +const { messages } = require('elasticio-node'); +const { callJSForceMethod } = require('../helpers/wrapper'); +const { getSecret, refreshToken } = require('../util'); +const { SALESFORCE_API_VERSION } = require('../common.js').globalConsts; +let fayeClient; /** * This method will be called from elastic.io platform providing following data * * @param msg incoming message object that contains ``body`` with payload - * @param cfg configuration that is account information and configuration field values + * @param configuration configuration that is account information and configuration field values */ -function processTrigger(msg, cfg) { - const emitError = (e) => { - this.logger.error(e); - this.emit('error', e); - }; - - const emitEnd = () => { - this.logger.info('Finished message processing'); - this.emit('end'); - }; - - const emitKeys = (res) => { - this.logger.info('Oauth tokens were updated'); - this.emit('updateKeys', { oauth: res }); - }; - - if (!conn) { - this.logger.info('Trying to connect to jsforce...'); - conn = new jsforce.Connection({ - oauth2: { - clientId: process.env.OAUTH_CLIENT_ID, - clientSecret: process.env.OAUTH_CLIENT_SECRET, - }, - instanceUrl: cfg.oauth.instance_url, - accessToken: cfg.oauth.access_token, - refreshToken: cfg.oauth.refresh_token, - version: common.globalConsts.SALESFORCE_API_VERSION, - }); - conn.on('refresh', (accessToken, res) => { - this.logger.debug('Keys were updated, res=%j', res); - emitKeys(res); - }); - - conn.on('error', err => emitError(err)); - const topic = `/event/${cfg.object}`; - const replayId = -1; - const fayeClient = conn.streaming.createClient([ +async function processTrigger(msg, configuration) { + this.logger.info('Starting Subscribe to platform events Trigger'); + const { secretId } = configuration; + if (!secretId) { + this.logger.error('secretId is missing in configuration, credentials cannot be fetched'); + throw new Error('secretId is missing in configuration, credentials cannot be fetched'); + } + this.logger.debug('Fetching credentials by secretId'); + const { credentials } = await getSecret(this, secretId); + const accessToken = credentials.access_token; + const instanceUrl = credentials.undefined_params.instance_url; + this.logger.trace('AccessToken = %s', accessToken); + this.logger.debug('Preparing SalesForce connection...'); + const connection = new jsforce.Connection({ + instanceUrl, + accessToken, + version: SALESFORCE_API_VERSION, + }); + const topic = `/event/${configuration.object}`; + const replayId = -1; + this.logger.debug('Creating streaming client'); + if (!fayeClient) { + fayeClient = connection.streaming.createClient([ new jsforce.StreamingExtension.Replay(topic, replayId), - new jsforce.StreamingExtension.AuthFailure((err) => { - emitError(new Error(`Unexpected error: ${JSON.stringify(err)}`)); + new jsforce.StreamingExtension.AuthFailure(async (err) => { + this.logger.trace('AuthFailure: %j', err); + if (err.ext && err.ext.sfdc && err.ext.sfdc.failureReason && (err.ext.sfdc.failureReason === '401::Authentication invalid')) { + try { + this.logger.debug('Session is expired, trying to refresh token'); + await refreshToken(this, secretId); + this.logger.debug('Token is successfully refreshed'); + } catch (error) { + this.logger.trace('Refresh token error: %j', error); + this.logger.error('Failed to fetch and/or refresh token'); + throw new Error('Failed to fetch and/or refresh token'); + } + fayeClient = undefined; + this.logger.info('Lets call processTrigger one more time'); + await processTrigger.call(this, msg, configuration); + } else { + this.logger.error('AuthFailure extension error occurred'); + throw err; + } }), ]); - fayeClient.subscribe(topic, (message) => { - this.logger.debug('Message: %j', message); - this.emit('data', messages.newMessageWithBody(message)); + fayeClient.subscribe(topic, async (message) => { + this.logger.info('Incoming message found, going to emit...'); + this.logger.trace('Incoming Message: %j', message); + await this.emit('data', messages.newMessageWithBody(message)); }) - .then(() => this.logger.info(`Subscribed to PushTopic: ${topic}`), - err => emitError(err)); + .then(() => { + this.logger.info('Subscribed to PushTopic successfully'); + this.logger.trace(`Subscribed to PushTopic: ${topic}`); + }, + (err) => { + this.logger.error('Subscriber error occurred'); + throw err; + }); + this.logger.info('Streaming client created and ready'); } - emitEnd(); } /** @@ -72,8 +80,7 @@ function processTrigger(msg, cfg) { * @param configuration */ async function getObjectTypes(configuration) { - const metaLoader = new MetaLoader(configuration, this); - return metaLoader.getPlatformEvents(); + return callJSForceMethod.call(this, configuration, 'getPlatformEvents'); } module.exports.process = processTrigger; diff --git a/lib/triggers/task.js b/lib/triggers/task.js deleted file mode 100644 index 147034c..0000000 --- a/lib/triggers/task.js +++ /dev/null @@ -1 +0,0 @@ -require('../entry.js').buildTrigger('Task', exports, '25.0'); diff --git a/lib/util.js b/lib/util.js index 01d2bfe..2768c2b 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,5 +1,7 @@ const axios = require('axios'); const client = require('elasticio-rest-node')(); +const { URL } = require('url'); +const path = require('path'); const REQUEST_TIMEOUT = process.env.REQUEST_TIMEOUT || 10000; // 10s const REQUEST_MAX_CONTENT_LENGTH = process.env.REQUEST_MAX_CONTENT_LENGTH || 10485760; // 10 MB @@ -19,10 +21,10 @@ function addRetryCountInterceptorToAxios(ax) { }); } - module.exports.base64Encode = (value) => Buffer.from(value).toString('base64'); module.exports.base64Decode = (value) => Buffer.from(value, 'base64').toString('utf-8'); module.exports.createSignedUrl = async () => client.resources.storage.createSignedUrl(); + module.exports.uploadAttachment = async (url, payload) => { const ax = axios.create(); addRetryCountInterceptorToAxios(ax); @@ -34,6 +36,7 @@ module.exports.uploadAttachment = async (url, payload) => { maxContentLength: REQUEST_MAX_CONTENT_LENGTH, }); }; + module.exports.downloadAttachment = async (url) => { const ax = axios.create(); addRetryCountInterceptorToAxios(ax); @@ -45,3 +48,48 @@ module.exports.downloadAttachment = async (url) => { }); return response.data; }; + +function getSecretUri(secretId, isRefresh) { + const parsedUrl = new URL(process.env.ELASTICIO_API_URI); + parsedUrl.username = process.env.ELASTICIO_API_USERNAME; + parsedUrl.password = process.env.ELASTICIO_API_KEY; + parsedUrl.pathname = path.join( + parsedUrl.pathname || '/', + 'v2/workspaces/', + process.env.ELASTICIO_WORKSPACE_ID, + 'secrets', + String(secretId), + isRefresh ? 'refresh' : '', + ); + return parsedUrl.toString(); +} + +module.exports.getSecret = async (emitter, secretId) => { + const secretUri = getSecretUri(secretId); + emitter.logger.info('Going to fetch secret'); + const ax = axios.create(); + addRetryCountInterceptorToAxios(ax); + const secret = await ax.get(secretUri, { + timeout: REQUEST_TIMEOUT, + retry: REQUEST_MAX_RETRY, + delay: REQUEST_RETRY_DELAY, + }); + const parsedSecret = secret.data.data.attributes; + emitter.logger.info('Got secret'); + return parsedSecret; +}; + +module.exports.refreshToken = async (emitter, secretId) => { + const secretUri = getSecretUri(secretId, true); + emitter.logger.info('going to refresh secret'); + const ax = axios.create(); + addRetryCountInterceptorToAxios(ax); + const secret = await ax.post(secretUri, {}, { + timeout: REQUEST_TIMEOUT, + retry: REQUEST_MAX_RETRY, + delay: REQUEST_RETRY_DELAY, + }); + const token = secret.data.data.attributes.credentials.access_token; + emitter.logger.info('Token refreshed'); + return token; +}; diff --git a/package-lock.json b/package-lock.json index e79e640..cccf22c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,67 @@ { "name": "salesforce-component", - "version": "1.2.3", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.10.4" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", - "esutils": "^2.0.2", "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@elastic.io/bunyan-logger": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@elastic.io/bunyan-logger/-/bunyan-logger-1.0.5.tgz", + "integrity": "sha512-FcoaG7nTA2H/VuE+0TC1ZKxwEMv3eTVJ42HwrzOY3x3UIpJ9RorG+Sk7G6SBoNuEiBRslGA6Iy9ddE4J3lR2og==", + "requires": { + "bunyan": "1.8.12" + }, + "dependencies": { + "bunyan": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", + "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "requires": { + "dtrace-provider": "~0.8", + "moment": "^2.10.6", + "mv": "~2", + "safe-json-stringify": "~1" + } + } } }, "@elastic.io/component-logger": { @@ -49,34 +89,114 @@ } } }, + "@elastic.io/object-storage-client": { + "version": "0.0.2-dev", + "resolved": "https://registry.npmjs.org/@elastic.io/object-storage-client/-/object-storage-client-0.0.2-dev.tgz", + "integrity": "sha512-jVra0BMYg5jktFtOFPaYmnW3LUTBUjoIAHlI3igPwEEOMeYtyAGKo2Mz7c9kNdoRsPLsYmGiyR7mH0nl6dlYvA==", + "requires": { + "@elastic.io/bunyan-logger": "1.0.5", + "axios": "0.19.0", + "get-stream": "5.1.0", + "jsonwebtoken": "8.5.1", + "uuid": "3.3.2" + }, + "dependencies": { + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "@eslint/eslintrc": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", + "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@sinonjs/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", "dev": true, "requires": { "type-detect": "4.0.8" } }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", "dev": true, "requires": { "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "@sinonjs/samsam": "^5.0.2" } }, "@sinonjs/samsam": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.2.tgz", - "integrity": "sha512-ILO/rR8LfAb60Y1Yfp9vxfYAASK43NFC2mLzpvLUbCQY/Qu8YwReboseu8aheCEkyElZF2L2T9mHcR2bgdvZyA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.1.0.tgz", + "integrity": "sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg==", "dev": true, "requires": { - "@sinonjs/commons": "^1.0.2", - "array-from": "^2.1.1", - "lodash": "^4.17.11" + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, "@sinonjs/text-encoding": { @@ -85,39 +205,46 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "accounting": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/accounting/-/accounting-0.4.1.tgz", "integrity": "sha1-h91BA+/39EYPHhhvXGd+1s9WaIM=" }, "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true }, "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, "amqplib": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz", @@ -130,21 +257,15 @@ } }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -177,20 +298,79 @@ "sprintf-js": "~1.0.2" } }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, "asap": { @@ -223,12 +403,6 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, - "async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", - "optional": true - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -240,9 +414,17 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, + "axios": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", + "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "requires": { + "follow-redirects": "^1.10.0" + } }, "balanced-match": { "version": "1.0.0", @@ -250,9 +432,9 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base64-url": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-2.2.1.tgz", - "integrity": "sha512-RWaW1M7+pLUikK1bnGyiDe1oY2BKOtbS30Ua1pSAH41st59qDxi/XiggjVhHVPIejXY1eqJ21W3uxHtZpM6KQw==" + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-2.3.3.tgz", + "integrity": "sha512-dLMhIsK7OplcDauDH/tZLvK7JmUZK3A7KiQpjNzsBrM6Etw7hzNI1tLEywqJk9NnwkgWuFKSlx/IUO7vF6Mo8Q==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -271,9 +453,9 @@ } }, "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "brace-expansion": { "version": "1.1.11", @@ -290,6 +472,11 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-more-ints": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", @@ -354,22 +541,66 @@ "type-detect": "^4.0.5" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "check-error": "^1.0.2" } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, "check-error": { "version": "1.0.2", @@ -377,21 +608,6 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -403,15 +619,19 @@ "wrap-ansi": "^5.1.0" }, "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "ansi-regex": "^4.1.0" } } } @@ -450,23 +670,29 @@ "dev": true }, "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true + }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -484,27 +710,14 @@ "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "csprng": { @@ -516,9 +729,9 @@ } }, "csv-parse": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-1.3.3.tgz", - "integrity": "sha1-0c/YdDwvhJoKuy/VRNtWaV0ZpJA=" + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.12.0.tgz", + "integrity": "sha512-wPQl3H79vWLPI8cgKFcQXl0NBgYYEqVnT1i6/So7OjMpsI540oD7p93r3w6fDSyPvwkTepG05F69/7AViX2lXg==" }, "csv-stringify": { "version": "1.1.2", @@ -537,10 +750,9 @@ } }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" } @@ -560,12 +772,6 @@ "type-detect": "^4.0.0" } }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -579,6 +785,14 @@ "dev": true, "requires": { "object-keys": "^1.0.12" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, "delayed-stream": { @@ -602,9 +816,9 @@ } }, "dotenv": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.0.0.tgz", - "integrity": "sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", "dev": true }, "dtrace-provider": { @@ -625,6 +839,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "elasticio-node": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/elasticio-node/-/elasticio-node-0.0.9.tgz", @@ -637,75 +859,93 @@ "request": "^2.85.0", "stream-counter": "1.0.0", "uuid": "3.0.1" - }, - "dependencies": { - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=" - }, - "uuid": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" - } } }, "elasticio-rest-node": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/elasticio-rest-node/-/elasticio-rest-node-1.2.3.tgz", - "integrity": "sha512-9OBmI/gKnIXOq8RJFO1VHHyaVn8oImSHdGE7EAZy9A6N7yBU0Y9qbtv6sA/VK7+NQprkFLvTxH3lHyy+BCgqkg==", - "requires": { - "lodash": "^3.10.1", - "natives": "^1.1.6", - "q": "^1.4.1", - "requestretry": "^3.1.0" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/elasticio-rest-node/-/elasticio-rest-node-1.2.6.tgz", + "integrity": "sha512-j9bdO1L9LmimBTQ6su7bakA5DqVbqdMtgXotX7+oCoPMLFClVTESRaokwDrK9MA15x5ZKrh/E4JNVqNxp83MdQ==", + "requires": { + "lodash": "4.17.15", + "q": "1.5.1", + "request": "2.88.2", + "requestretry": "4.1.0" }, "dependencies": { "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" } } }, "elasticio-sailor-nodejs": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/elasticio-sailor-nodejs/-/elasticio-sailor-nodejs-2.6.2.tgz", - "integrity": "sha512-JZOqB83hnj76+p00ft+5PR8I+jGXNgIhXPLGBfl1sIR5eb/7vTx73YBunvTmSqxzMsiyjFCGog91EhZfNvgg3g==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/elasticio-sailor-nodejs/-/elasticio-sailor-nodejs-2.6.14.tgz", + "integrity": "sha512-7hMSpSYOD+uN0ZUiUvAXDxAfn979CBfu59IC76aL1IYyj82YXyz3GI3n4/1ssyK2PWSp765KZ1FiquiAd2BWBw==", "requires": { + "@elastic.io/object-storage-client": "0.0.2-dev", "amqplib": "0.5.1", "bunyan": "1.8.10", "co": "4.6.0", "debug": "3.1.0", - "elasticio-rest-node": "1.2.3", + "elasticio-rest-node": "1.2.5", "event-to-promise": "0.8.0", - "lodash": "4.17.4", + "lodash": "4.17.15", "p-throttle": "2.1.0", "q": "1.4.1", "request-promise-native": "1.0.5", "requestretry": "3.1.0", - "self-addressed": "0.3.0", "uuid": "3.0.1" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "elasticio-rest-node": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/elasticio-rest-node/-/elasticio-rest-node-1.2.5.tgz", + "integrity": "sha512-aLrUBMMgjEzsz0pzxxDYAx3mjtL2gC8Hybsn91lKh2fOQrXe/3+uw88jumZR/QZnsJS/pjAIxrom9Ks26Dms+A==", "requires": { - "ms": "2.0.0" + "lodash": "4.17.15", + "q": "1.5.1", + "request": "2.88.2", + "requestretry": "4.1.0" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "requestretry": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-4.1.0.tgz", + "integrity": "sha512-q3IT2vz5vkcMT6xgwB/BWzsmnu7N/27l9fW86U48gt9Mwrce5rSEyFvpAW7Il1/B78/NBUlYBvcCY1RzWUWy7w==", + "requires": { + "extend": "^3.0.2", + "lodash": "^4.17.10", + "when": "^3.7.7" + } + } } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=" + "requestretry": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-3.1.0.tgz", + "integrity": "sha512-DkvCPK6qvwxIuVA5TRCvi626WHC2rWjF/n7SCQvVHAr2JX9i1/cmIpSEZlmHAo+c1bj9rjaKoZ9IsKwCpTkoXA==", + "requires": { + "extend": "^3.0.2", + "lodash": "^4.17.10", + "when": "^3.7.7" + } } } }, @@ -715,6 +955,23 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -725,23 +982,37 @@ } }, "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", "dev": true, "requires": { - "es-to-primitive": "^1.2.0", + "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -749,11 +1020,6 @@ "is-symbol": "^1.0.2" } }, - "es6-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz", - "integrity": "sha1-zMSWPmefDKn7GHx3e55YPTx1c8I=" - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -761,55 +1027,50 @@ "dev": true }, "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.9.0.tgz", + "integrity": "sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "@eslint/eslintrc": "^0.1.3", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^1.3.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", + "levn": "^0.4.1", + "lodash": "^4.17.19", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", "table": "^5.2.3", - "text-table": "^0.2.0" + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -819,87 +1080,114 @@ "ms": "^2.1.1" } }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true } } }, + "eslint-config-airbnb": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz", + "integrity": "sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^14.2.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2" + } + }, "eslint-config-airbnb-base": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz", - "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz", + "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==", "dev": true, "requires": { - "eslint-restricted-globals": "^0.1.1", + "confusing-browser-globals": "^1.0.9", "object.assign": "^4.1.0", - "object.entries": "^1.0.4" + "object.entries": "^1.1.2" } }, "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { "debug": "^2.6.9", - "resolve": "^1.5.0" + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, "eslint-module-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", - "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "requires": { - "debug": "^2.6.8", + "debug": "^2.6.9", "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, "eslint-plugin-import": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.0.tgz", - "integrity": "sha512-PZpAEC4gj/6DEMMoU2Df01C5c50r7zdGIN52Yfi7CvvWaYssG7Jt5R9nFG5gmqodxNOz9vQS87xk6Izdtpdrig==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", + "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", "dev": true, "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.0", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", "has": "^1.0.3", - "lodash": "^4.17.11", "minimatch": "^3.0.4", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.11.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", @@ -918,43 +1206,40 @@ } } }, - "eslint-restricted-globals": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", - "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", - "dev": true - }, "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } }, "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", "dev": true, "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "acorn": "^7.4.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.3.0" } }, "esprima": { @@ -964,33 +1249,49 @@ "dev": true }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "event-to-promise": { @@ -1003,31 +1304,20 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -1036,34 +1326,26 @@ "dev": true }, "faye": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/faye/-/faye-1.2.4.tgz", - "integrity": "sha1-l47YpY8dSB5cH5i6y4lZ3l7FxkM=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/faye/-/faye-1.4.0.tgz", + "integrity": "sha512-kRrIg4be8VNYhycS2PY//hpBJSzZPr/DBbcy9VWelhZMW3KhyLkQR0HL0k0MNpmVoNFF4EdfMFkNAWjTP65g6w==", "requires": { "asap": "*", "csprng": "*", "faye-websocket": ">=0.9.1", + "safe-buffer": "*", "tough-cookie": "*", "tunnel-agent": "*" } }, "faye-websocket": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", - "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", "requires": { "websocket-driver": ">=0.5.1" } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -1074,12 +1356,12 @@ } }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "^2.0.0" } }, "flat": { @@ -1103,9 +1385,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -1128,18 +1410,15 @@ } }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, - "forEachAsync": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/forEachAsync/-/forEachAsync-2.2.1.tgz", - "integrity": "sha1-43I/AJA5EOHrSx2zrVG1xkoxn+w=", - "requires": { - "sequence": "2.x" - } + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" }, "forever-agent": { "version": "0.6.1", @@ -1186,6 +1465,14 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "requires": { + "pump": "^3.0.0" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -1207,16 +1494,28 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } }, "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "growl": { @@ -1225,27 +1524,17 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "handlebars": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-3.0.0.tgz", - "integrity": "sha1-f05Tf03WmShp1mwBt1BeujVhpdU=", - "requires": { - "optimist": "^0.6.1", - "source-map": "^0.1.40", - "uglify-js": "~2.3" - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, @@ -1265,20 +1554,11 @@ "dev": true }, "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, - "hbs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/hbs/-/hbs-3.1.1.tgz", - "integrity": "sha1-qmqGo+r0yy3Kgj7RuRRQDpwlgv0=", - "requires": { - "handlebars": "3.0.0", - "walk": "2.2.1" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1286,15 +1566,15 @@ "dev": true }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "http-parser-js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", - "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" }, "http-signature": { "version": "1.2.0", @@ -1306,15 +1586,6 @@ "sshpk": "^1.7.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -1322,9 +1593,9 @@ "dev": true }, "import-fresh": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -1347,47 +1618,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "inquirer": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.4.1.tgz", - "integrity": "sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-arrayish": { "version": "0.2.1", @@ -1398,19 +1631,24 @@ "is-buffer": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", "dev": true }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { @@ -1419,28 +1657,43 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", "dev": true }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { - "has": "^1.0.1" + "has-symbols": "^1.0.1" } }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "^1.0.1" } }, "is-typedarray": { @@ -1476,9 +1729,9 @@ "dev": true }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -1491,19 +1744,19 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsforce": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/jsforce/-/jsforce-1.9.1.tgz", - "integrity": "sha512-AP4wVnz8guvF8zvHdk2xA2DZxvOq3MGbLNPu7tPVOyO38D1qt+psWpYmHgncjkmy9LoSk58K+dU56YE38zqMlg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/jsforce/-/jsforce-1.10.0.tgz", + "integrity": "sha512-d6CPBo76G0Ts7qJySktZMviHvfj2f2kJV+zaGuP1bioRhkvMcfvvpYL9G0xvgd8DX7zw5aMN4xsVeLD8skxFcA==", "requires": { "base64-url": "^2.2.0", "co-prompt": "^1.0.0", "coffeescript": "^1.10.0", "commander": "^2.9.0", - "csv-parse": "^1.1.1", + "csv-parse": "^4.10.1", "csv-stringify": "^1.0.4", "faye": "^1.2.0", "inherits": "^2.0.1", - "lodash": "^4.11.1", + "lodash": "^4.17.19", "multistream": "^2.0.5", "opn": "^5.3.0", "promise": "^7.1.1", @@ -1518,9 +1771,9 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1531,6 +1784,11 @@ "util-deprecate": "~1.0.1" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -1562,6 +1820,39 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1574,24 +1865,43 @@ } }, "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", "dev": true }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keypress": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz", "integrity": "sha1-HoBFQlABjbrUw/6USX1uZ7YmnHc=" }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, "load-json-file": { @@ -1607,25 +1917,66 @@ } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "^3.0.0", + "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -1633,33 +1984,34 @@ "dev": true, "requires": { "chalk": "^2.0.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, - "lolex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.1.0.tgz", - "integrity": "sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==", - "dev": true - }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "requires": { - "mime-db": "1.40.0" + "mime-db": "1.44.0" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1669,22 +2021,22 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" } }, "mocha": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", - "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -1699,7 +2051,7 @@ "js-yaml": "3.13.1", "log-symbols": "2.2.0", "minimatch": "3.0.4", - "mkdirp": "0.5.1", + "mkdirp": "0.5.4", "ms": "2.1.1", "node-environment-flags": "1.0.5", "object.assign": "4.1.0", @@ -1707,11 +2059,17 @@ "supports-color": "6.0.0", "which": "1.3.1", "wide-align": "1.1.3", - "yargs": "13.3.0", - "yargs-parser": "13.1.1", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", "yargs-unparser": "1.6.0" }, "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -1721,6 +2079,15 @@ "ms": "^2.1.1" } }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -1735,27 +2102,113 @@ "path-is-absolute": "^1.0.0" } }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } - } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.28.0.tgz", + "integrity": "sha512-Z5KOjYmnHyd/ukynmFd/WwyXHd7L4J9vTI/nn5Ap9AVUgaAE15VvQ9MOGmJJygEUklupqIrFnor/tjTwRU+tQw==" }, "ms": { "version": "2.0.0", @@ -1777,9 +2230,9 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1790,6 +2243,11 @@ "util-deprecate": "~1.0.1" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -1800,12 +2258,6 @@ } } }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -1818,16 +2270,11 @@ } }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "optional": true }, - "natives": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", - "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==" - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -1840,40 +2287,29 @@ "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "nise": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.0.tgz", - "integrity": "sha512-Z3sfYEkLFzFmL8KY6xnSJLRxwQwYBjOXi/24lb62ZnZiGA0JUzGGTI6TBIgfCSMIDl9Jlu8SRmHNACLTemDHww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", "dev": true, "requires": { - "@sinonjs/formatio": "^3.1.0", + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^4.1.0", "path-to-regexp": "^1.7.0" } }, "nock": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", - "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.4.tgz", + "integrity": "sha512-alqTV8Qt7TUbc74x1pKRLSENzfjp4nywovcJgi/1aXDiUxXdt7TkruSTF5MDWPP7UoPVgea4F9ghVdmX0xxnSA==", "dev": true, "requires": { - "chai": "^4.1.2", "debug": "^4.1.0", - "deep-equal": "^1.0.0", "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.5", - "mkdirp": "^0.5.0", - "propagate": "^1.0.0", - "qs": "^6.5.1", - "semver": "^5.5.0" + "lodash.set": "^4.3.2", + "propagate": "^2.0.0" }, "dependencies": { "debug": { @@ -1903,11 +2339,6 @@ "semver": "^5.7.0" } }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -1926,39 +2357,73 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true }, "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", "dev": true }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, "object.entries": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", - "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", + "es-abstract": "^1.17.5", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, "object.getownpropertydescriptors": { @@ -1972,55 +2437,68 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", - "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } } } }, @@ -2032,15 +2510,6 @@ "wrappy": "1" } }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -2049,59 +2518,36 @@ "is-wsl": "^1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - } + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "^1.0.0" } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "^1.1.0" } }, "p-throttle": { @@ -2110,9 +2556,9 @@ "integrity": "sha512-DvChtxq2k1PfiK4uZXKA4IvRyuq/gP55tb6MQyMLGfYJifCjJY5lDMb94IQHZss/K/tmZx3fAsSC1IqP0e1OnA==" }, "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "parent-module": { @@ -2144,16 +2590,10 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { @@ -2163,9 +2603,9 @@ "dev": true }, "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dev": true, "requires": { "isarray": "0.0.1" @@ -2204,63 +2644,18 @@ "dev": true, "requires": { "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } } }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -2277,15 +2672,24 @@ } }, "propagate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", - "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, "punycode": { "version": "2.1.1", @@ -2293,9 +2697,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=" }, "qs": { "version": "6.5.2", @@ -2321,51 +2725,6 @@ "requires": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } } }, "readable-stream": { @@ -2380,15 +2739,15 @@ } }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -2397,7 +2756,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -2407,35 +2766,45 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, "dependencies": { "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" } } }, "request-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz", - "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", + "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", "requires": { "bluebird": "^3.5.0", - "request-promise-core": "1.1.2", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" + }, + "dependencies": { + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + } + } } }, "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", "requires": { - "lodash": "^4.17.11" + "lodash": "^4.13.1" } }, "request-promise-native": { @@ -2446,22 +2815,12 @@ "request-promise-core": "1.1.1", "stealthy-require": "^1.1.0", "tough-cookie": ">=2.3.3" - }, - "dependencies": { - "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", - "requires": { - "lodash": "^4.13.1" - } - } } }, "requestretry": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-3.1.0.tgz", - "integrity": "sha512-DkvCPK6qvwxIuVA5TRCvi626WHC2rWjF/n7SCQvVHAr2JX9i1/cmIpSEZlmHAo+c1bj9rjaKoZ9IsKwCpTkoXA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-4.1.0.tgz", + "integrity": "sha512-q3IT2vz5vkcMT6xgwB/BWzsmnu7N/27l9fW86U48gt9Mwrce5rSEyFvpAW7Il1/B78/NBUlYBvcCY1RzWUWy7w==", "requires": { "extend": "^3.0.2", "lodash": "^4.17.10", @@ -2481,9 +2840,9 @@ "dev": true }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -2495,16 +2854,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -2514,28 +2863,10 @@ "glob": "^6.0.1" } }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-json-stringify": { "version": "1.2.0", @@ -2553,24 +2884,10 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, - "self-addressed": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/self-addressed/-/self-addressed-0.3.0.tgz", - "integrity": "sha1-AitQYD5zh9poVmG8OW8pp/hxXgs=", - "requires": { - "es6-promise": "2.0.1" - } - }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "sequence": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/sequence/-/sequence-2.2.1.tgz", - "integrity": "sha1-f1YXiV1ENRwKBH52RGdpBJChawM=" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "sequin": { "version": "0.1.1", @@ -2584,39 +2901,56 @@ "dev": true }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "sinon": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", - "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.1", - "diff": "^3.5.0", - "lolex": "^4.0.1", - "nise": "^1.4.10", - "supports-color": "^5.5.0" + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.3.tgz", + "integrity": "sha512-IKo9MIM111+smz9JGwLmw5U1075n1YXeAq8YeSFlndCLhAL5KGn6bLgu7b/4AYHTV/LcEMcRm2wU2YiL55/6Pg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.2", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.1.0", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "slice-ansi": { @@ -2630,18 +2964,10 @@ "is-fullwidth-code-point": "^2.0.0" } }, - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "requires": { - "amdefine": ">=0.0.4" - } - }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -2649,15 +2975,15 @@ } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -2665,9 +2991,9 @@ } }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", "dev": true }, "sprintf-js": { @@ -2703,50 +3029,105 @@ "integrity": "sha1-kc8lac5NxQYf6816yyY5SloRR1E=" }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { + "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "strip-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, "string_decoder": { @@ -2755,12 +3136,12 @@ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" } }, "strip-bom": { @@ -2770,9 +3151,9 @@ "dev": true }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { @@ -2785,43 +3166,15 @@ } }, "table": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.1.tgz", - "integrity": "sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "ajv": "^6.9.1", - "lodash": "^4.17.11", + "ajv": "^6.10.2", + "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } } }, "text-table": { @@ -2830,43 +3183,27 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "os-tmpdir": "~1.0.2" + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" } }, - "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2881,12 +3218,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-detect": { @@ -2895,32 +3232,16 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, - "uglify-js": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz", - "integrity": "sha1-+gmEdwtCi3qbKoBY9GNV0U/vIRo=", - "optional": true, - "requires": { - "async": "~0.2.6", - "optimist": "~0.3.5", - "source-map": "~0.1.7" - }, - "dependencies": { - "optimist": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", - "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", - "optional": true, - "requires": { - "wordwrap": "~0.0.2" - } - } - } + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { "punycode": "^2.1.0" } @@ -2935,6 +3256,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -2955,27 +3282,20 @@ "extsprintf": "^1.2.0" } }, - "walk": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/walk/-/walk-2.2.1.tgz", - "integrity": "sha1-WtofjknkfUt0Rdi+ei4eYxq0MBY=", - "requires": { - "forEachAsync": "~2.2" - } - }, "websocket-driver": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", - "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "requires": { - "http-parser-js": ">=0.4.0", + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, "when": { "version": "3.7.8", @@ -2983,9 +3303,9 @@ "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -3004,12 +3324,40 @@ "dev": true, "requires": { "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true }, "wrap-ansi": { "version": "5.1.0", @@ -3022,15 +3370,19 @@ "strip-ansi": "^5.0.0" }, "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "ansi-regex": "^4.1.0" } } } @@ -3050,18 +3402,18 @@ } }, "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "requires": { "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "xmlbuilder": "~11.0.0" } }, "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, "xtend": { "version": "2.1.2", @@ -3070,14 +3422,6 @@ "dev": true, "requires": { "object-keys": "~0.4.0" - }, - "dependencies": { - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - } } }, "y18n": { @@ -3087,9 +3431,9 @@ "dev": true }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { "cliui": "^5.0.0", @@ -3101,26 +3445,58 @@ "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "yargs-parser": "^13.1.2" }, "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true } } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -3136,14 +3512,6 @@ "flat": "^4.1.0", "lodash": "^4.17.15", "yargs": "^13.3.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } } } } diff --git a/package.json b/package.json index 3d5f6fb..70060f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salesforce-component", - "version": "1.3.5", + "version": "2.0.0", "description": "elastic.io component that connects to Salesforce API (node.js)", "main": "index.js", "scripts": { @@ -24,31 +24,30 @@ }, "homepage": "https://github.com/elasticio/salesforce-component#readme", "engines": { - "node": "12.3.0" + "node": "12.18.2" }, "dependencies": { - "axios": "0.19.2", - "dotenv": "8.0.0", + "axios": "0.20.0", "elasticio-node": "0.0.9", - "elasticio-rest-node": "1.2.3", - "elasticio-sailor-nodejs": "2.6.2", - "hbs": "3.1.1", - "jsforce": "1.9.1", - "lodash": "4.17.13", - "mime-types": "2.1.24", - "node-uuid": "1.4.8", - "q": "1.5.1", - "request": "2.88.0", - "request-promise": "4.2.4" + "elasticio-rest-node": "1.2.6", + "elasticio-sailor-nodejs": "2.6.14", + "jsforce": "1.10.0", + "mime-types": "2.1.27", + "request": "2.88.2", + "request-promise": "4.2.6" }, "devDependencies": { "@elastic.io/component-logger": "0.0.1", "chai": "4.2.0", - "eslint": "5.16.0", - "eslint-config-airbnb-base": "13.1.0", - "eslint-plugin-import": "2.18.0", - "mocha": "6.2.2", - "nock": "10.0.6", - "sinon": "7.3.2" + "chai-as-promised": "7.1.1", + "dotenv": "8.2.0", + "eslint": "7.9.0", + "eslint-config-airbnb": "18.2.0", + "eslint-config-airbnb-base": "14.2.0", + "eslint-plugin-import": "2.22.0", + "lodash": "4.17.20", + "mocha": "6.2.3", + "nock": "13.0.4", + "sinon": "9.0.3" } } diff --git a/spec-integration/actions/createObject.spec.js b/spec-integration/actions/createObject.spec.js new file mode 100644 index 0000000..9c645a0 --- /dev/null +++ b/spec-integration/actions/createObject.spec.js @@ -0,0 +1,71 @@ +/* eslint-disable no-return-assign */ +const fs = require('fs'); +const logger = require('@elastic.io/component-logger')(); +const { expect } = require('chai'); +const sinon = require('sinon'); +const nock = require('nock'); +const action = require('../../lib/actions/createObject'); + +describe('creare object action', async () => { + let emitter; + const secretId = 'secretId'; + let configuration; + let secret; + + before(async () => { + emitter = { + emit: sinon.spy(), + logger, + }; + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); + } + process.env.ELASTICIO_API_URI = 'https://app.example.io'; + process.env.ELASTICIO_API_USERNAME = 'user'; + process.env.ELASTICIO_API_KEY = 'apiKey'; + process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; + secret = { + data: { + attributes: { + credentials: { + access_token: process.env.ACCESS_TOKEN, + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + + configuration = { + secretId, + }; + + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .times(10) + .reply(200, secret); + }); + afterEach(() => { + emitter.emit.resetHistory(); + }); + + it('should succeed selectModel objectTypes', async () => { + const result = await action.objectTypes.call(emitter, configuration); + expect(result.Contact).to.eql('Contact'); + }); + + it('should succeed process create sobject=Contact', async () => { + const cfg = { + ...configuration, + sobject: 'Contact', + }; + const message = { + body: { + LastName: 'IntegrationTest', + }, + }; + const result = await action.process.call(emitter, message, cfg); + expect(result.body.LastName).to.eql('IntegrationTest'); + expect(Object.prototype.hasOwnProperty.call(result.body, 'id')).to.eql(true); + }); +}); diff --git a/spec-integration/actions/deleteObject.spec.js b/spec-integration/actions/deleteObject.spec.js index 4b69cb5..ee97f54 100644 --- a/spec-integration/actions/deleteObject.spec.js +++ b/spec-integration/actions/deleteObject.spec.js @@ -1,36 +1,70 @@ /* eslint-disable no-return-assign,no-unused-expressions */ - +const fs = require('fs'); const sinon = require('sinon'); const chai = require('chai'); +const nock = require('nock'); const logger = require('@elastic.io/component-logger')(); const deleteObject = require('../../lib/actions/deleteObject'); -const { testDataFactory } = require('../../lib/helpers/deleteObjectHelpers.js'); +// const { testDataFactory } = require('../../lib/helpers/deleteObjectHelpers.js'); const { expect } = chai; describe('Delete Object Integration Functionality', () => { let emitter; let testObjLst; + const secretId = 'secretId'; + let configuration; + let secret; before(async () => { - // eslint-disable-next-line global-require - require('dotenv').config({ path: `${__dirname}/.env` }); - emitter = { emit: sinon.spy(), logger, }; - - testObjLst = await testDataFactory.call(emitter); - if (!(testObjLst)) { - throw Error('Test data was not successfully created'); + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); } + process.env.ELASTICIO_API_URI = 'https://app.example.io'; + process.env.ELASTICIO_API_USERNAME = 'user'; + process.env.ELASTICIO_API_KEY = 'apiKey'; + process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; + secret = { + data: { + attributes: { + credentials: { + access_token: process.env.ACCESS_TOKEN, + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + + configuration = { + secretId, + sobject: 'Contact', + }; + + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .times(10) + .reply(200, secret); }); beforeEach(() => { emitter.emit.resetHistory(); }); + it('Delete object by Id my config', async () => { + const message = { + body: { + Id: '0032R00002AHsqLQAT', + }, + }; + const result = await deleteObject.process.call(emitter, message, { ...configuration, lookupField: 'Id' }); + expect(result.body.id).to.be.eq('id'); + }); + it('Correctly identifies a lack of response on a non-existent Contact', async () => { await deleteObject.process.call(emitter, testObjLst[0].message, testObjLst[0].config); expect(emitter.emit.args[2][1].body).to.be.empty; diff --git a/lib/helpers/deleteObjectHelpers.js b/spec-integration/actions/deleteObjectHelpers.js similarity index 96% rename from lib/helpers/deleteObjectHelpers.js rename to spec-integration/actions/deleteObjectHelpers.js index 2d2f004..122b8c9 100644 --- a/lib/helpers/deleteObjectHelpers.js +++ b/spec-integration/actions/deleteObjectHelpers.js @@ -2,7 +2,7 @@ const { messages } = require('elasticio-node'); const createObj = require('../../lib/actions/createObject.js'); -const deleteObjectData = require('../../spec-integration/actions/deleteObject.json'); +const deleteObjectData = require('./deleteObject.json'); /** * Des: diff --git a/spec-integration/actions/lookup.spec.js b/spec-integration/actions/lookup.spec.js deleted file mode 100644 index 8874640..0000000 --- a/spec-integration/actions/lookup.spec.js +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint-disable no-return-assign */ -const fs = require('fs'); -const sinon = require('sinon'); -const { messages } = require('elasticio-node'); -const chai = require('chai'); -const logger = require('@elastic.io/component-logger')(); -const lookup = require('../../lib/actions/lookup'); - -const { expect } = chai; - -describe('lookup', () => { - let message; - let lastCall; - let configuration; - - beforeEach(async () => { - lastCall.reset(); - }); - - before(async () => { - if (fs.existsSync('.env')) { - // eslint-disable-next-line global-require - require('dotenv').config(); - } - - lastCall = sinon.stub(messages, 'newMessageWithBody') - .returns(Promise.resolve()); - - configuration = { - apiVersion: '39.0', - oauth: { - instance_url: 'https://na38.salesforce.com', - refresh_token: process.env.REFRESH_TOKEN, - access_token: process.env.ACCESS_TOKEN, - }, - prodEnv: 'login', - sobject: 'Contact', - _account: '5be195b7c99b61001068e1d0', - lookupField: 'AccountId', - batchSize: 2, - }; - message = { - body: { - AccountId: '0014400001ytQUJAA2', - }, - }; - }); - - after(async () => { - messages.newMessageWithBody.restore(); - }); - - const emitter = { - emit: sinon.spy(), - logger, - }; - - it('lookup Contacts ', async () => { - await lookup.process.call(emitter, message, configuration) - .then(() => { - expect(lastCall.lastCall.args[0].result[0].attributes.type).to.eql('Contact'); - }); - }); - - it('Contact Lookup fields ', async () => { - await lookup.getLookupFieldsModel(configuration) - .then((result) => { - expect(result).to.be.deep.eql({ - Id: 'Contact ID', - IndividualId: 'Individual ID', - MasterRecordId: 'Master Record ID', - AccountId: 'Account ID', - ReportsToId: 'Reports To ID', - OwnerId: 'Owner ID', - CreatedById: 'Created By ID', - Demo_Email__c: 'Demo Email', - LastModifiedById: 'Last Modified By ID', - extID__c: 'extID', - }); - }); - }); - it('Contact objectTypes ', async () => { - await lookup.objectTypes.call(emitter, configuration) - .then((result) => { - expect(result.Account).to.be.eql('Account'); - }); - }); - - it('Contact Lookup Meta', async () => { - await lookup.getMetaModel.call(emitter, configuration) - .then((result) => { - expect(result.in).to.be.deep.eql({ - type: 'object', - properties: { - AccountId: { - type: 'string', required: false, title: 'Account ID', default: null, - }, - }, - }); - expect(result.out.properties.Id).to.be.deep.eql( - { - type: 'string', required: false, title: 'Contact ID', default: null, - }, - ); - }); - }); -}); diff --git a/spec-integration/actions/query.spec.js b/spec-integration/actions/query.spec.js new file mode 100644 index 0000000..012c666 --- /dev/null +++ b/spec-integration/actions/query.spec.js @@ -0,0 +1,93 @@ +/* eslint-disable no-return-assign */ +const fs = require('fs'); +const logger = require('@elastic.io/component-logger')(); +const { expect } = require('chai'); +const sinon = require('sinon'); +const nock = require('nock'); +const action = require('../../lib/actions/query'); + +describe('query action', async () => { + let emitter; + const secretId = 'secretId'; + let configuration; + let secret; + let invalidSecret; + const message = { + body: { + query: 'SELECT Id, Name FROM Contact limit 10', + }, + }; + + before(async () => { + emitter = { + emit: sinon.spy(), + logger, + }; + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); + } + process.env.ELASTICIO_API_URI = 'https://app.example.io'; + process.env.ELASTICIO_API_USERNAME = 'user'; + process.env.ELASTICIO_API_KEY = 'apiKey'; + process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; + secret = { + data: { + attributes: { + credentials: { + access_token: process.env.ACCESS_TOKEN, + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + invalidSecret = { + data: { + attributes: { + credentials: { + access_token: 'access_token', + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + + configuration = { + secretId, + }; + }); + afterEach(() => { + emitter.emit.resetHistory(); + }); + + it('should succeed query allowResultAsSet', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .reply(200, secret); + await action.process.call(emitter, message, { ...configuration, allowResultAsSet: true }); + expect(emitter.emit.callCount).to.eql(1); + expect(emitter.emit.args[0][0]).to.eql('data'); + expect(emitter.emit.args[0][1].body.result.length).to.eql(10); + }); + + it('should succeed query batchSize', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .reply(200, secret); + await action.process.call(emitter, message, { ...configuration, batchSize: 3 }); + expect(emitter.emit.callCount).to.eql(4); + expect(emitter.emit.args[0][0]).to.eql('data'); + expect(emitter.emit.args[0][1].body.result.length).to.eql(3); + }); + + it('should refresh token query', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .reply(200, invalidSecret) + .post(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}/refresh`) + .reply(200, secret); + await action.process.call(emitter, message, configuration); + expect(emitter.emit.callCount).to.eql(10); + expect(emitter.emit.args[0][0]).to.eql('data'); + }); +}); diff --git a/spec-integration/actions/upsert.new.spec.js b/spec-integration/actions/upsert.new.spec.js new file mode 100644 index 0000000..5413082 --- /dev/null +++ b/spec-integration/actions/upsert.new.spec.js @@ -0,0 +1,92 @@ +/* eslint-disable no-return-assign */ +const fs = require('fs'); +const logger = require('@elastic.io/component-logger')(); +const { expect } = require('chai'); +const sinon = require('sinon'); +const nock = require('nock'); +const action = require('../../lib/actions/upsert'); + +describe('upsert action', async () => { + let emitter; + const secretId = 'secretId'; + let configuration; + let secret; + + before(async () => { + emitter = { + emit: sinon.spy(), + logger, + }; + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); + } + process.env.ELASTICIO_API_URI = 'https://app.example.io'; + process.env.ELASTICIO_API_USERNAME = 'user'; + process.env.ELASTICIO_API_KEY = 'apiKey'; + process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; + secret = { + data: { + attributes: { + credentials: { + access_token: process.env.ACCESS_TOKEN, + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + + configuration = { + secretId, + }; + + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .times(10) + .reply(200, secret); + }); + afterEach(() => { + emitter.emit.resetHistory(); + }); + + it('should succeed selectModel objectTypes', async () => { + const result = await action.objectTypes.call(emitter, configuration); + expect(result.Contact).to.eql('Contact'); + }); + + it('should succeed process sobject=Contact by extId', async () => { + const cfg = { + ...configuration, + sobject: 'Contact', + extIdField: 'extID__c', + }; + const message = { + body: { + extID__c: 'watson', + email: 'watsonExtId@test.com', + }, + }; + await action.process.call(emitter, message, cfg); + expect(emitter.emit.callCount).to.eql(1); + expect(emitter.emit.args[0][0]).to.eql('data'); + expect(emitter.emit.args[0][1].body.extID__c).to.eql('watson'); + expect(emitter.emit.args[0][1].body.Email).to.eql('watsonextid@test.com'); + }); + + it('should succeed process sobject=Contact by Id', async () => { + const cfg = { + ...configuration, + sobject: 'Contact', + }; + const message = { + body: { + Id: '0032R00002AIXAvQAP', + email: 'watsonid@test.com', + }, + }; + await action.process.call(emitter, message, cfg); + expect(emitter.emit.callCount).to.eql(1); + expect(emitter.emit.args[0][0]).to.eql('data'); + expect(emitter.emit.args[0][1].body.Email).to.eql('watsonid@test.com'); + }); +}); diff --git a/spec-integration/helpers/attachment.spec.js b/spec-integration/helpers/attachment.spec.js new file mode 100644 index 0000000..f668af5 --- /dev/null +++ b/spec-integration/helpers/attachment.spec.js @@ -0,0 +1,80 @@ +/* eslint-disable no-return-assign */ +const fs = require('fs'); +const logger = require('@elastic.io/component-logger')(); +const { expect } = require('chai'); +const nock = require('nock'); +const { prepareBinaryData } = require('../../lib/helpers/attachment'); + +describe('attachment helper test', async () => { + const secretId = 'secretId'; + let configuration; + let secret; + + before(async () => { + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); + } + process.env.ELASTICIO_API_URI = 'https://app.example.io'; + process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; + secret = { + data: { + attributes: { + credentials: { + access_token: process.env.ACCESS_TOKEN, + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + + configuration = { + secretId, + sobject: 'Document', + }; + }); + describe('prepareBinaryData test', async () => { + it('should discard attachment utilizeAttachment:false', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .reply(200, secret); + const msg = { + body: { + Name: 'TryTest', + }, + attachments: { + 'Fox.jpeg': { + 'content-type': 'image/jpeg', + size: 126564, + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + }, + }, + }; + await prepareBinaryData(msg, { ...configuration, utilizeAttachment: false }, { logger }); + expect(msg.body.Name).to.eql('TryTest'); + expect(Object.prototype.hasOwnProperty.call(msg.body, 'Body')).to.eql(false); + }); + + it('should upload attachment utilizeAttachment:true', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .reply(200, secret); + const msg = { + body: { + Name: 'TryTest', + }, + attachments: { + 'Fox.jpeg': { + 'content-type': 'image/jpeg', + size: 126564, + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + }, + }, + }; + await prepareBinaryData(msg, { ...configuration, utilizeAttachment: true }, { logger }); + expect(msg.body.Name).to.eql('TryTest'); + expect(msg.body.ContentType).to.eql('image/jpeg'); + expect(Object.prototype.hasOwnProperty.call(msg.body, 'Body')).to.eql(true); + }); + }); +}); diff --git a/spec-integration/helpers/wrapper.spec.js b/spec-integration/helpers/wrapper.spec.js new file mode 100644 index 0000000..aa0e40d --- /dev/null +++ b/spec-integration/helpers/wrapper.spec.js @@ -0,0 +1,79 @@ +/* eslint-disable no-return-assign */ +const fs = require('fs'); +const logger = require('@elastic.io/component-logger')(); +const { expect } = require('chai'); +const nock = require('nock'); +const { callJSForceMethod } = require('../../lib/helpers/wrapper'); + +describe('wrapper helper test', async () => { + const secretId = 'secretId'; + let configuration; + let secret; + let invalidSecret; + + before(async () => { + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); + } + process.env.ELASTICIO_API_URI = 'https://app.example.io'; + process.env.ELASTICIO_API_USERNAME = 'user'; + process.env.ELASTICIO_API_KEY = 'apiKey'; + process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; + secret = { + data: { + attributes: { + credentials: { + access_token: process.env.ACCESS_TOKEN, + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + invalidSecret = { + data: { + attributes: { + credentials: { + access_token: 'access_token', + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }; + + configuration = { + secretId, + sobject: 'Contact', + }; + }); + + it('should succeed call describe method, credentials from config', async () => { + const cfg = { + sobject: 'Contact', + oauth: { + access_token: process.env.ACCESS_TOKEN, + instance_url: process.env.INSTANCE_URL, + }, + }; + const result = await callJSForceMethod.call({ logger }, cfg, 'describe'); + expect(result.name).to.eql('Contact'); + }); + + it('should succeed call describe method', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .reply(200, secret); + const result = await callJSForceMethod.call({ logger }, configuration, 'describe'); + expect(result.name).to.eql('Contact'); + }); + + it('should refresh token and succeed call describe method', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .reply(200, invalidSecret) + .post(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}/refresh`) + .reply(200, secret); + const result = await callJSForceMethod.call({ logger }, configuration, 'describe'); + expect(result.name).to.eql('Contact'); + }); +}); diff --git a/spec-integration/triggers/polling.spec.js b/spec-integration/triggers/polling.spec.js index 1f80081..835a256 100644 --- a/spec-integration/triggers/polling.spec.js +++ b/spec-integration/triggers/polling.spec.js @@ -53,7 +53,6 @@ describe('polling', () => { .process.call(emitter, message, configuration, snapshot); }); - it('Account polling with not empty snapshot', async () => { snapshot = '2018-11-12T13:06:01.179Z'; await polling diff --git a/spec-integration/triggers/streamPlatformEvents.spec.js b/spec-integration/triggers/streamPlatformEvents.spec.js new file mode 100644 index 0000000..15d814a --- /dev/null +++ b/spec-integration/triggers/streamPlatformEvents.spec.js @@ -0,0 +1,67 @@ +/* eslint-disable no-return-assign */ +const fs = require('fs'); +const logger = require('@elastic.io/component-logger')(); +const { expect } = require('chai'); +const sinon = require('sinon'); +const nock = require('nock'); +const trigger = require('../../lib/triggers/streamPlatformEvents'); + +describe('streamPlatformEvents trigger test', async () => { + let emitter; + const secretId = 'secretId'; + let configuration; + let secret; + + before(async () => { + emitter = { + emit: sinon.spy(), + logger, + }; + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); + } + process.env.ELASTICIO_API_URI = 'https://app.example.io'; + process.env.ELASTICIO_API_USERNAME = 'user'; + process.env.ELASTICIO_API_KEY = 'apiKey'; + process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; + secret = { + data: { + attributes: { + credentials: { + access_token: process.env.ACCESS_TOKEN, + undefined_params: { + instance_url: process.env.INSTANCE_URL, + }, + }, + }, + }, + }; + + configuration = { + secretId, + object: 'Test__e', + }; + + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${secretId}`) + .times(10) + .reply(200, secret); + }); + afterEach(() => { + emitter.emit.resetHistory(); + }); + + it('should succeed selectModel objectTypes', async () => { + const result = await trigger.objectTypes.call(emitter, configuration); + expect(result).to.eql({ + Test__e: 'Integration Test event', + UserCreateAcknowledge__e: 'UserCreateAcknowledge', + }); + }); + + it('should succeed process trigger', async () => { + await trigger.process.call(emitter, {}, configuration); + expect(emitter.emit.callCount).to.eql(0); + }); +}); diff --git a/spec-integration/verifyCredentials.spec.js b/spec-integration/verifyCredentials.spec.js new file mode 100644 index 0000000..2668d7f --- /dev/null +++ b/spec-integration/verifyCredentials.spec.js @@ -0,0 +1,31 @@ +/* eslint-disable no-return-assign */ +const fs = require('fs'); +const logger = require('@elastic.io/component-logger')(); +const { expect } = require('chai'); +const verify = require('../verifyCredentials'); + +describe('verifyCredentials', async () => { + let configuration; + + before(async () => { + if (fs.existsSync('.env')) { + // eslint-disable-next-line global-require + require('dotenv').config(); + } + + configuration = { + oauth: { + undefined_params: { + instance_url: process.env.INSTANCE_URL, + }, + refresh_token: process.env.REFRESH_TOKEN, + access_token: process.env.ACCESS_TOKEN, + }, + }; + }); + + it('should succeed', async () => { + const result = await verify.call({ logger }, configuration); + expect(result.verified).to.eql(true); + }); +}); diff --git a/spec/actions/bulk_cud.spec.js b/spec/actions/bulk_cud.spec.js index 1438e42..1aa8407 100644 --- a/spec/actions/bulk_cud.spec.js +++ b/spec/actions/bulk_cud.spec.js @@ -3,20 +3,19 @@ const chai = require('chai'); const nock = require('nock'); const testCommon = require('../common.js'); -const testData = require('./bulk_cud.json'); +const testData = require('../testData/bulk_cud.json'); const bulk = require('../../lib/actions/bulk_cud.js'); -nock.disableNetConnect(); - - describe('Salesforce bulk', () => { beforeEach(async () => { - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .reply(200, testCommon.secret); }); - + afterEach(() => { + nock.cleanAll(); + }); it('action create', async () => { const data = testData.bulkInsertCase; data.configuration = { ...testCommon.configuration, ...data.configuration }; @@ -47,7 +46,6 @@ describe('Salesforce bulk', () => { } }); - it('action update', async () => { const data = testData.bulkUpdateCase; data.configuration = { ...testCommon.configuration, ...data.configuration }; @@ -72,7 +70,6 @@ describe('Salesforce bulk', () => { } }); - it('action delete', async () => { const data = testData.bulkDeleteCase; data.configuration = { ...testCommon.configuration, ...data.configuration }; diff --git a/spec/actions/bulk_q.spec.js b/spec/actions/bulk_q.spec.js index ea730f7..81867bc 100644 --- a/spec/actions/bulk_q.spec.js +++ b/spec/actions/bulk_q.spec.js @@ -3,19 +3,19 @@ const chai = require('chai'); const nock = require('nock'); const testCommon = require('../common.js'); -const testData = require('./bulk_q.json'); +const testData = require('../testData/bulk_q.json'); const bulk = require('../../lib/actions/bulk_q.js'); -nock.disableNetConnect(); - - describe('Salesforce bulk query', () => { beforeEach(async () => { - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .reply(200, testCommon.secret); }); + afterEach(() => { + nock.cleanAll(); + }); it('action query', async () => { const data = testData.bulkQuery; diff --git a/spec/actions/createObject.spec.js b/spec/actions/createObject.spec.js index ac84fdb..df49e16 100644 --- a/spec/actions/createObject.spec.js +++ b/spec/actions/createObject.spec.js @@ -4,206 +4,206 @@ const _ = require('lodash'); const common = require('../../lib/common.js'); const testCommon = require('../common.js'); -const objectTypesReply = require('../sfObjects.json'); -const metaModelDocumentReply = require('../sfDocumentMetadata.json'); -const metaModelAccountReply = require('../sfAccountMetadata.json'); +const objectTypesReply = require('../testData/sfObjects.json'); +const metaModelDocumentReply = require('../testData/sfDocumentMetadata.json'); +const metaModelAccountReply = require('../testData/sfAccountMetadata.json'); const createObject = require('../../lib/actions/createObject.js'); -// Disable real HTTP requests -nock.disableNetConnect(); +describe('Create Object action test', () => { + beforeEach(() => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(2) + .reply(200, testCommon.secret); + }); + afterEach(() => { + nock.cleanAll(); + }); + describe('Create Object module: objectTypes', () => { + it('Retrieves the list of createable sobjects', async () => { + const scope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) + .reply(200, objectTypesReply); + + const expectedResult = {}; + objectTypesReply.sobjects.forEach((object) => { + if (object.createable) expectedResult[object.name] = object.label; + }); -describe('Create Object module: objectTypes', () => { - it('Retrieves the list of createable sobjects', async () => { - const scope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) - .reply(200, objectTypesReply); + const result = await createObject.objectTypes.call(testCommon, testCommon.configuration); + chai.expect(result).to.deep.equal(expectedResult); - const expectedResult = {}; - objectTypesReply.sobjects.forEach((object) => { - if (object.createable) expectedResult[object.name] = object.label; + scope.done(); }); - - const result = await createObject.objectTypes.call(testCommon, testCommon.configuration); - chai.expect(result).to.deep.equal(expectedResult); - - scope.done(); }); -}); -describe('Create Object module: getMetaModel', () => { - function testMetaData(object, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) - .reply(200, getMetaModelReply); - - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); - - const expectedResult = { - in: { - description: object, - type: 'object', - properties: {}, - }, - }; - getMetaModelReply.fields.forEach((field) => { - if (field.createable) { - const fieldDescriptor = { - title: field.label, - custom: field.custom, - default: field.defaultValue, - type: (() => { - switch (field.soapType) { - case 'xsd:boolean': return 'boolean'; - case 'xsd:double': return 'number'; - case 'xsd:int': return 'integer'; - default: return 'string'; - } - })(), - required: !field.nillable && !field.defaultedOnCreate, - readonly: field.calculated || !field.updateable, - }; - - if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { - fieldDescriptor.enum = []; - field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); - } + describe('Create Object module: getMetaModel', async () => { + async function testMetaData(object, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) + .reply(200, getMetaModelReply); - expectedResult.in.properties[field.name] = fieldDescriptor; - } - }); - expectedResult.out = _.cloneDeep(expectedResult.in); - expectedResult.out.properties.id = { - type: 'string', - required: true, - readonly: true, - title: 'ObjectID', - }; - - return new Promise(((resolve, reject) => { - testCommon.configuration.sobject = object; - createObject.getMetaModel.call(testCommon, testCommon.configuration, (err, data) => { - if (err) reject(err); - - resolve(data); + const expectedResult = { + in: { + type: 'object', + properties: {}, + }, + }; + getMetaModelReply.fields.forEach((field) => { + if (field.createable) { + const fieldDescriptor = { + title: field.label, + default: field.defaultValue, + type: (() => { + switch (field.soapType) { + case 'xsd:boolean': return 'boolean'; + case 'xsd:double': return 'number'; + case 'xsd:int': return 'number'; + default: return 'string'; + } + })(), + required: !field.nillable && !field.defaultedOnCreate, + }; + if (field.type === 'textarea') { + fieldDescriptor.maxLength = 1000; + } + + if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { + fieldDescriptor.enum = []; + field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); + } + + expectedResult.in.properties[field.name] = fieldDescriptor; + } }); - })).then((data) => { + expectedResult.out = _.cloneDeep(expectedResult.in); + expectedResult.out.properties.id = { + type: 'string', + required: true, + }; + testCommon.configuration.sobject = object; + const data = await createObject.getMetaModel.call(testCommon, testCommon.configuration); chai.expect(data).to.deep.equal(expectedResult); sfScope.done(); - // sfRefreshTokenScope.done(); + } + + it('Retrieves metadata for Document object', async () => { + const object = 'Document'; + await testMetaData(object, metaModelDocumentReply); }); - } - it('Retrieves metadata for Document object', testMetaData.bind(null, 'Document', metaModelDocumentReply)); - it('Retrieves metadata for Account object', testMetaData.bind(null, 'Account', metaModelAccountReply)); -}); + it('Retrieves metadata for Account object', async () => { + const object = 'Account'; + await testMetaData(object, metaModelAccountReply); + }); + }); -describe('Create Object module: createObject', () => { - it('Sends request for Account creation', async () => { - const message = { - body: { - Name: 'Fred', - BillingStreet: 'Elm Street', - }, - }; - - nock(testCommon.configuration.oauth.instance_url) - .post(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account`, message.body) - .reply(200, { - id: 'new_account_id', - success: true, - }); + describe('Create Object module: createObject', () => { + it('Sends request for Account creation', async () => { + const message = { + body: { + Name: 'Fred', + BillingStreet: 'Elm Street', + }, + }; - testCommon.configuration.sobject = 'Account'; - const result = await createObject.process - .call(testCommon, _.cloneDeep(message), testCommon.configuration); + nock(testCommon.instanceUrl) + .post(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account`, message.body) + .reply(200, { + id: 'new_account_id', + success: true, + }); - message.body.id = 'new_account_id'; - chai.expect(result.body).to.deep.equal(message.body); - }); + testCommon.configuration.sobject = 'Account'; + const result = await createObject.process + .call(testCommon, _.cloneDeep(message), testCommon.configuration); + + message.body.id = 'new_account_id'; + chai.expect(result.body).to.deep.equal(message.body); + }); - it('Sends request for Document creation not using input attachment', async () => { - const message = { - body: { - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'not quite binary data', - ContentType: 'application/octet-stream', - }, - attachments: { - theFile: { - url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - 'content-type': 'image/jpeg', + it('Sends request for Document creation not using input attachment', async () => { + const message = { + body: { + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'not quite binary data', + ContentType: 'application/octet-stream', }, - }, - }; - - nock(testCommon.configuration.oauth.instance_url) - .post(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document`, message.body) - .reply(200, { - id: 'new_document_id', - success: true, - }); + attachments: { + theFile: { + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + 'content-type': 'image/jpeg', + }, + }, + }; - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.utilizeAttachment = false; + nock(testCommon.instanceUrl) + .post(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document`, message.body) + .reply(200, { + id: 'new_document_id', + success: true, + }); - const result = await createObject.process - .call(testCommon, _.cloneDeep(message), testCommon.configuration); + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.utilizeAttachment = false; - message.body.id = 'new_document_id'; - chai.expect(result.body).to.deep.equal(message.body); - }); + const result = await createObject.process + .call(testCommon, _.cloneDeep(message), testCommon.configuration); + + message.body.id = 'new_document_id'; + chai.expect(result.body).to.deep.equal(message.body); + }); - it('Sends request for Document creation using input attachment', async () => { - const message = { - body: { - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'not quite binary data', - ContentType: 'application/octet-stream', - }, - attachments: { - theFile: { - url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - 'content-type': 'image/jpeg', + it('Sends request for Document creation using input attachment', async () => { + const message = { + body: { + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'not quite binary data', + ContentType: 'application/octet-stream', }, - }, - }; + attachments: { + theFile: { + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + 'content-type': 'image/jpeg', + }, + }, + }; - const resultRequestBody = _.cloneDeep(message.body); - resultRequestBody.Body = Buffer.from(JSON.stringify(message)).toString('base64'); // Take the message as binary data - resultRequestBody.ContentType = message.attachments.theFile['content-type']; + const resultRequestBody = _.cloneDeep(message.body); + resultRequestBody.Body = Buffer.from(JSON.stringify(message)).toString('base64'); // Take the message as binary data + resultRequestBody.ContentType = message.attachments.theFile['content-type']; - const newDocID = 'new_document_id'; + const newDocID = 'new_document_id'; - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .post(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document`, resultRequestBody) - .reply(200, { - id: newDocID, - success: true, - }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply); + const sfScope = nock(testCommon.instanceUrl) + .post(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document`, resultRequestBody) + .reply(200, { + id: newDocID, + success: true, + }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply); - const binaryScope = nock('https://upload.wikimedia.org') - .get('/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg') - .reply(200, JSON.stringify(message)); + const binaryScope = nock('https://upload.wikimedia.org') + .get('/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg') + .reply(200, JSON.stringify(message)); - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.utilizeAttachment = true; + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.utilizeAttachment = true; - const result = await createObject.process - .call(testCommon, _.cloneDeep(message), testCommon.configuration); + const result = await createObject.process + .call(testCommon, _.cloneDeep(message), testCommon.configuration); - resultRequestBody.id = newDocID; - delete resultRequestBody.Body; - chai.expect(result.body).to.deep.equal(resultRequestBody); + resultRequestBody.id = newDocID; + delete resultRequestBody.Body; + chai.expect(result.body).to.deep.equal(resultRequestBody); - sfScope.done(); - binaryScope.done(); + sfScope.done(); + binaryScope.done(); + }); }); }); diff --git a/spec/actions/deleteObject.spec.js b/spec/actions/deleteObject.spec.js index e968b27..d8196b3 100644 --- a/spec/actions/deleteObject.spec.js +++ b/spec/actions/deleteObject.spec.js @@ -1,243 +1,223 @@ -/* eslint-disable no-restricted-syntax,guard-for-in,func-names */ - const _ = require('lodash'); const chai = require('chai'); const nock = require('nock'); -const sinon = require('sinon'); +const { expect } = chai; const common = require('../../lib/common.js'); const testCommon = require('../common.js'); -const testDeleteData = require('./deleteObject.json'); -const deleteObjectAction = require('../../lib/actions/deleteObject.js'); -const helpers = require('../../lib/helpers/deleteObjectHelpers.js'); - -const metaModelDocumentReply = require('../sfDocumentMetadata.json'); -const metaModelAccountReply = require('../sfAccountMetadata.json'); - -nock.disableNetConnect(); -const { expect } = chai; +const deleteObjectAction = require('../../lib/actions/deleteObject.js'); +const metaModelDocumentReply = require('../testData/sfDocumentMetadata.json'); +const metaModelAccountReply = require('../testData/sfAccountMetadata.json'); + +describe('Delete Object (at most 1) action', () => { + before(() => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(10) + .reply(200, testCommon.secret); + }); + describe('Delete Object (at most 1) module: getMetaModel', () => { + async function testMetaData(configuration, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${configuration.sobject}/describe`) + .reply(200, getMetaModelReply); + + const expectedResult = { + in: { + type: 'object', + properties: {}, + }, + out: { + type: 'object', + properties: { + response: { + type: 'object', + properties: { + id: { + title: 'id', + type: 'string', + }, + success: { + title: 'success', + type: 'boolean', + }, + errors: { + title: 'errors', + type: 'array', + }, + }, + }, + }, + }, + }; -describe('Delete Object (at most 1) module: getMetaModel', () => { - function testMetaData(configuration, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${configuration.sobject}/describe`) - .reply(200, getMetaModelReply); + if (configuration.lookupField) { + getMetaModelReply.fields.forEach((field) => { + const fieldDescriptor = { + title: field.label, + default: field.defaultValue, + type: (() => { + switch (field.soapType) { + case 'xsd:boolean': + return 'boolean'; + case 'xsd:double': + return 'number'; + case 'xsd:int': + return 'number'; + case 'urn:address': + return 'object'; + default: + return 'string'; + } + })(), + required: !field.nillable && !field.defaultedOnCreate, + }; + + if (field.soapType === 'urn:address') { + fieldDescriptor.properties = { + city: { type: 'string' }, + country: { type: 'string' }, + postalCode: { type: 'string' }, + state: { type: 'string' }, + street: { type: 'string' }, + }; + } - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); + if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; - const expectedResult = { - in: { - type: 'object', - properties: {}, - }, - out: { - type: 'object', - properties: {}, - }, - }; - - getMetaModelReply.fields.forEach((field) => { - const fieldDescriptor = { - title: field.label, - default: field.defaultValue, - type: (() => { - switch (field.soapType) { - case 'xsd:boolean': return 'boolean'; - case 'xsd:double': return 'number'; - case 'xsd:int': return 'number'; - case 'urn:address': return 'object'; - default: return 'string'; + if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { + fieldDescriptor.enum = []; + field.picklistValues.forEach((pick) => { + fieldDescriptor.enum.push(pick.value); + }); } - })(), - required: !field.nillable && !field.defaultedOnCreate, - }; - if (field.soapType === 'urn:address') { - fieldDescriptor.properties = { - city: { type: 'string' }, - country: { type: 'string' }, - postalCode: { type: 'string' }, - state: { type: 'string' }, - street: { type: 'string' }, + if (configuration.lookupField === field.name) { + expectedResult.in.properties[field.name] = { ...fieldDescriptor, required: true }; + } + }); + } else { + expectedResult.in.properties = { + id: { + title: 'Object ID', + type: 'string', + required: true, + }, }; } - if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; - - if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { - fieldDescriptor.enum = []; - field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); - } - - if (configuration.lookupField === field.name) { - expectedResult.in.properties[field.name] = { ...fieldDescriptor, required: true }; + const data = await deleteObjectAction.getMetaModel.call(testCommon, configuration); + expect(data).to.deep.equal(expectedResult); + if (configuration.lookupField) { + sfScope.done(); } + } - expectedResult.out.properties[field.name] = fieldDescriptor; + it('Retrieves metadata for Document object', async () => { + await testMetaData({ + ...testCommon.configuration, + sobject: 'Document', + lookupField: 'Id', + }, + metaModelDocumentReply); }); - return deleteObjectAction.getMetaModel.call(testCommon, configuration) - .then((data) => { - expect(data).to.deep.equal(expectedResult); - sfScope.done(); - }); - } - - it('Retrieves metadata for Document object', testMetaData.bind(null, { - ...testCommon.configuration, - sobject: 'Document', - lookupField: 'Id', - }, - metaModelDocumentReply)); - - it('Retrieves metadata for Account object', testMetaData.bind(null, { - ...testCommon.configuration, - sobject: 'Account', - lookupField: 'Id', - }, - metaModelAccountReply)); -}); - -describe('Delete Object (at most 1) module: process', () => { - it('Deletes a document object without an attachment', async () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.lookupField = 'Id'; - - const message = { - body: { - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'image/jpeg', + it('Retrieves metadata for Account object without lookupField', async () => { + await testMetaData({ + ...testCommon.configuration, + sobject: 'Account', }, - }; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) - .reply(200, { done: true, totalSize: 1, records: [message.body] }); - - const stub = sinon.stub(helpers, 'deleteObjById'); - stub.resolves(true); - await deleteObjectAction.process.call( - testCommon, _.cloneDeep(message), testCommon.configuration, - ); - expect(stub.calledOnce).to.equal(true); - stub.restore(); - scope.done(); + metaModelAccountReply); + }); }); - it('Deletes a document object with an attachment', async () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.lookupField = 'Id'; - - const message = { - body: { - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: '/upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'image/jpeg', - }, - }; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) - .reply(200, { done: true, totalSize: 1, records: [message.body] }); - - nock(testCommon.EXT_FILE_STORAGE).put('', JSON.stringify(message)).reply(200); - - const stub = sinon.stub(helpers, 'deleteObjById'); - stub.resolves(true); - await deleteObjectAction.process.call( - testCommon, _.cloneDeep(message), testCommon.configuration, - ); - expect(stub.calledOnce).to.equal(true); - stub.restore(); - scope.done(); - }); -}); + describe('Delete Object (at most 1) module: process', () => { + it('Deletes a document object without an attachment', async () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.lookupField = 'Id'; + + const message = { + body: { + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'image/jpeg', + }, + }; -describe('Delete Object (at most 1) module: getLookupFieldsModel', () => { - async function testUniqueFields(object, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) - .reply(200, getMetaModelReply); + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, + { Id: message.body.Id })}`) + .reply(200, { done: true, totalSize: 1, records: [message.body] }) + .delete(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/testObjId`) + .reply(200, { id: 'deletedId' }); + + const result = await deleteObjectAction.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + expect(result.body).to.deep.equal({ response: { id: 'deletedId' } }); + scope.done(); + }); - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); + it('Deletes a document object with an attachment', async () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.lookupField = 'Id'; + + const message = { + body: { + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: '/upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'image/jpeg', + }, + }; - const expectedResult = {}; - getMetaModelReply.fields.forEach((field) => { - if (field.type === 'id' || field.unique) expectedResult[field.name] = `${field.label} (${field.name})`; - }); + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, + { Id: message.body.Id })}`) + .reply(200, { done: true, totalSize: 1, records: [message.body] }) + .delete(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/testObjId`) + .reply(200, { id: 'deletedId' }); - testCommon.configuration.typeOfSearch = 'uniqueFields'; - testCommon.configuration.sobject = object; + nock(testCommon.EXT_FILE_STORAGE).put('/', JSON.stringify(message)).reply(200); - const result = await deleteObjectAction.getLookupFieldsModel.call( - testCommon, testCommon.configuration, - ); + const result = await deleteObjectAction.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + expect(result.body).to.deep.equal({ response: { id: 'deletedId' } }); + scope.done(); + }); + }); - chai.expect(result).to.deep.equal(expectedResult); - sfScope.done(); - } + describe('Delete Object (at most 1) module: getLookupFieldsModel', () => { + async function testUniqueFields(object, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) + .reply(200, getMetaModelReply); - it('Retrieves the list of unique fields of specified sobject', testUniqueFields.bind(null, 'Document', metaModelDocumentReply)); -}); + const expectedResult = {}; + getMetaModelReply.fields.forEach((field) => { + if (field.type === 'id' || field.unique) expectedResult[field.name] = `${field.label} (${field.name})`; + }); -describe('Delete Object (at most 1) module: deleteObjById', () => { - async function testDeleteDataFunc(id, objType, expectedRes) { - const method = () => new Promise((resolve) => { resolve(expectedRes); }); - const sfConnSpy = sinon.spy(method); - const sfConn = { - sobject: () => ({ - delete: sfConnSpy, - }), - }; - - const getResult = new Promise((resolve) => { - testCommon.emitCallback = function (what, msg) { - if (what === 'data') resolve(msg); - }; - }); + testCommon.configuration.typeOfSearch = 'uniqueFields'; + testCommon.configuration.sobject = object; - await helpers.deleteObjById.call(testCommon, sfConn, id, objType); - const actualRes = await getResult; + const result = await deleteObjectAction.getLookupFieldsModel.call( + testCommon, testCommon.configuration, + ); - expect(actualRes).to.have.all.keys(expectedRes); - expect(sfConnSpy.calledOnceWithExactly(id)).to.equal(true); - } + chai.expect(result).to.deep.equal(expectedResult); + sfScope.done(); + } - it('properly deletes an object and emits the correct data based on a Case obj', async () => { - await testDeleteDataFunc( - testDeleteData.cases[0].body.response.id, - testDeleteData.cases[0].body.request.sobject, - testDeleteData.cases[0], - ); - }); - it('properly deletes an object and emits the correct data based on an Account obj', async () => { - await testDeleteDataFunc( - testDeleteData.cases[1].body.response.id, - testDeleteData.cases[1].body.request.sobject, - testDeleteData.cases[1], - ); - }); - it('properly deletes an object and emits the correct data based on a Custom SalesForce sObject', async () => { - await testDeleteDataFunc( - testDeleteData.cases[2].body.response.id, - testDeleteData.cases[2].body.request.sobject, - testDeleteData.cases[2], - ); + it('Retrieves the list of unique fields of specified sobject', async () => { + await testUniqueFields.bind(null, 'Document', metaModelDocumentReply); + }); }); }); diff --git a/spec/actions/lookupObject.spec.js b/spec/actions/lookupObject.spec.js index fe54e58..f37b9c4 100644 --- a/spec/actions/lookupObject.spec.js +++ b/spec/actions/lookupObject.spec.js @@ -4,265 +4,274 @@ const _ = require('lodash'); const common = require('../../lib/common.js'); const testCommon = require('../common.js'); -const objectTypesReply = require('../sfObjects.json'); -const metaModelDocumentReply = require('../sfDocumentMetadata.json'); -const metaModelAccountReply = require('../sfAccountMetadata.json'); +const objectTypesReply = require('../testData/sfObjects.json'); +const metaModelDocumentReply = require('../testData/sfDocumentMetadata.json'); +const metaModelAccountReply = require('../testData/sfAccountMetadata.json'); process.env.HASH_LIMIT_TIME = 1000; const lookupObject = require('../../lib/actions/lookupObject.js'); -// Disable real HTTP requests -nock.disableNetConnect(); +describe('Lookup Object (at most 1) test', () => { + beforeEach(() => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(4) + .reply(200, testCommon.secret); + }); + afterEach(() => { + nock.cleanAll(); + }); + describe('Lookup Object (at most 1) module: objectTypes', () => { + it('Retrieves the list of queryable sobjects', async () => { + const scope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) + .reply(200, objectTypesReply); + + const expectedResult = {}; + objectTypesReply.sobjects.forEach((object) => { + if (object.queryable) expectedResult[object.name] = object.label; + }); -describe('Lookup Object (at most 1) module: objectTypes', () => { - it('Retrieves the list of queryable sobjects', async () => { - const scope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) - .reply(200, objectTypesReply); + const result = await lookupObject.objectTypes.call(testCommon, testCommon.configuration); + chai.expect(result).to.deep.equal(expectedResult); - const expectedResult = {}; - objectTypesReply.sobjects.forEach((object) => { - if (object.queryable) expectedResult[object.name] = object.label; + scope.done(); }); - - const result = await lookupObject.objectTypes.call(testCommon, testCommon.configuration); - chai.expect(result).to.deep.equal(expectedResult); - - scope.done(); }); -}); - -describe('Lookup Object (at most 1) module: getLookupFieldsModel', () => { - async function testUniqueFields(object, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) - .reply(200, getMetaModelReply); - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); - - const expectedResult = {}; - getMetaModelReply.fields.forEach((field) => { - if (field.type === 'id' || field.unique) expectedResult[field.name] = `${field.label} (${field.name})`; - }); + describe('Lookup Object (at most 1) module: getLookupFieldsModel', () => { + async function testUniqueFields(object, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) + .reply(200, getMetaModelReply); - testCommon.configuration.typeOfSearch = 'uniqueFields'; - testCommon.configuration.sobject = object; + nock(testCommon.refresh_token.url) + .post('/') + .reply(200, testCommon.refresh_token.response); - const result = await lookupObject.getLookupFieldsModel - .call(testCommon, testCommon.configuration); + const expectedResult = {}; + getMetaModelReply.fields.forEach((field) => { + if (field.type === 'id' || field.unique) expectedResult[field.name] = `${field.label} (${field.name})`; + }); - chai.expect(result).to.deep.equal(expectedResult); - sfScope.done(); - } + testCommon.configuration.typeOfSearch = 'uniqueFields'; + testCommon.configuration.sobject = object; - it('Retrieves the list of unique fields of specified sobject', testUniqueFields.bind(null, 'Document', metaModelDocumentReply)); -}); + const result = await lookupObject.getLookupFieldsModel + .call(testCommon, testCommon.configuration); -describe('Lookup Object (at most 1) module: getLinkedObjectsModel', () => { - async function testLinkedObjects(object, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) - .reply(200, getMetaModelReply); + chai.expect(result).to.deep.equal(expectedResult); + sfScope.done(); + } - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); + it('Retrieves the list of unique fields of specified sobject', testUniqueFields.bind(null, 'Document', metaModelDocumentReply)); + }); - const expectedResult = { - Folder: 'Folder, User (Folder)', - Author: 'User (Author)', - CreatedBy: 'User (CreatedBy)', - LastModifiedBy: 'User (LastModifiedBy)', - }; + describe('Lookup Object (at most 1) module: getLinkedObjectsModel', () => { + async function testLinkedObjects(object, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) + .reply(200, getMetaModelReply); + + nock(testCommon.refresh_token.url) + .post('/') + .reply(200, testCommon.refresh_token.response); + + const expectedResult = { + Folder: 'Folder, User (Folder)', + Author: 'User (Author)', + CreatedBy: 'User (CreatedBy)', + LastModifiedBy: 'User (LastModifiedBy)', + }; - testCommon.configuration.typeOfSearch = 'uniqueFields'; - testCommon.configuration.sobject = object; + testCommon.configuration.typeOfSearch = 'uniqueFields'; + testCommon.configuration.sobject = object; - const result = await lookupObject.getLinkedObjectsModel - .call(testCommon, testCommon.configuration); + const result = await lookupObject.getLinkedObjectsModel + .call(testCommon, testCommon.configuration); - chai.expect(result).to.deep.equal(expectedResult); - sfScope.done(); - } + chai.expect(result).to.deep.equal(expectedResult); + sfScope.done(); + } - it('Retrieves the list of linked objects (their relationshipNames) from the specified object', testLinkedObjects.bind(null, 'Document', metaModelDocumentReply)); -}); + // eslint-disable-next-line max-len + it('Retrieves the list of linked objects (their relationshipNames) from the specified object', testLinkedObjects.bind(null, 'Document', metaModelDocumentReply)); + }); -describe('Lookup Object module: getMetaModel', () => { - function testMetaData(configuration, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${configuration.sobject}/describe`) - .reply(200, getMetaModelReply); - - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); - - const expectedResult = { - in: { - type: 'object', - properties: {}, - }, - out: { - type: 'object', - properties: {}, - }, - }; - - getMetaModelReply.fields.forEach((field) => { - const fieldDescriptor = { - title: field.label, - default: field.defaultValue, - type: (() => { - switch (field.soapType) { - case 'xsd:boolean': return 'boolean'; - case 'xsd:double': return 'number'; - case 'xsd:int': return 'number'; - case 'urn:address': return 'object'; - default: return 'string'; - } - })(), - required: !field.nillable && !field.defaultedOnCreate, + describe('Lookup Object module: getMetaModel', () => { + function testMetaData(configuration, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${configuration.sobject}/describe`) + .reply(200, getMetaModelReply); + + nock(testCommon.refresh_token.url) + .post('/') + .reply(200, testCommon.refresh_token.response); + + const expectedResult = { + in: { + type: 'object', + properties: {}, + }, + out: { + type: 'object', + properties: {}, + }, }; - if (field.soapType === 'urn:address') { - fieldDescriptor.properties = { - city: { type: 'string' }, - country: { type: 'string' }, - postalCode: { type: 'string' }, - state: { type: 'string' }, - street: { type: 'string' }, + getMetaModelReply.fields.forEach((field) => { + const fieldDescriptor = { + title: field.label, + default: field.defaultValue, + type: (() => { + switch (field.soapType) { + case 'xsd:boolean': return 'boolean'; + case 'xsd:double': return 'number'; + case 'xsd:int': return 'number'; + case 'urn:address': return 'object'; + default: return 'string'; + } + })(), + required: !field.nillable && !field.defaultedOnCreate, }; - } - if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; + if (field.soapType === 'urn:address') { + fieldDescriptor.properties = { + city: { type: 'string' }, + country: { type: 'string' }, + postalCode: { type: 'string' }, + state: { type: 'string' }, + street: { type: 'string' }, + }; + } + + if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; + + if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { + fieldDescriptor.enum = []; + field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); + } + + if (configuration.lookupField === field.name) { + expectedResult.in.properties[field.name] = { + ...fieldDescriptor, required: !configuration.allowCriteriaToBeOmitted, + }; + } + + expectedResult.out.properties[field.name] = fieldDescriptor; + }); - if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { - fieldDescriptor.enum = []; - field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); - } + return lookupObject.getMetaModel.call(testCommon, configuration) + .then((data) => { + chai.expect(data).to.deep.equal(expectedResult); + sfScope.done(); + }); + } + + it('Retrieves metadata for Document object', testMetaData.bind(null, { + ...testCommon.configuration, + sobject: 'Document', + lookupField: 'Id', + allowCriteriaToBeOmitted: true, + }, + metaModelDocumentReply)); + + it('Retrieves metadata for Account object', testMetaData.bind(null, { + ...testCommon.configuration, + sobject: 'Account', + lookupField: 'Id', + allowCriteriaToBeOmitted: true, + }, + metaModelAccountReply)); + }); - if (configuration.lookupField === field.name) { - expectedResult.in.properties[field.name] = { - ...fieldDescriptor, required: !configuration.allowCriteriaToBeOmitted, - }; - } + describe('Lookup Object module: processAction', () => { + it('Gets a Document object without its attachment', async () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.lookupField = 'Id'; + testCommon.configuration.allowCriteriaToBeOmitted = false; + testCommon.configuration.allowZeroResults = false; + testCommon.configuration.passBinaryData = false; + + const message = { + body: { + Id: 'testObjIdd', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'image/jpeg', + }, + }; - expectedResult.out.properties[field.name] = fieldDescriptor; - }); + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + // eslint-disable-next-line max-len + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}%20ORDER%20BY%20LastModifiedDate%20ASC`) + .reply(200, { done: true, totalSize: 1, records: [message.body] }); - return lookupObject.getMetaModel.call(testCommon, configuration) - .then((data) => { - chai.expect(data).to.deep.equal(expectedResult); - sfScope.done(); + const getResult = new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') resolve(msg); + }; }); - } - - it('Retrieves metadata for Document object', testMetaData.bind(null, { - ...testCommon.configuration, - sobject: 'Document', - lookupField: 'Id', - allowCriteriaToBeOmitted: true, - }, - metaModelDocumentReply)); - - it('Retrieves metadata for Account object', testMetaData.bind(null, { - ...testCommon.configuration, - sobject: 'Account', - lookupField: 'Id', - allowCriteriaToBeOmitted: true, - }, - metaModelAccountReply)); -}); - -describe('Lookup Object module: processAction', () => { - it('Gets a Document object without its attachment', async () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.lookupField = 'Id'; - testCommon.configuration.allowCriteriaToBeOmitted = false; - testCommon.configuration.allowZeroResults = false; - testCommon.configuration.passBinaryData = false; - - const message = { - body: { - Id: 'testObjIdd', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'image/jpeg', - }, - }; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) - .reply(200, { done: true, totalSize: 1, records: [message.body] }); - - - const getResult = new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') resolve(msg); - }; - }); - await lookupObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - const result = await getResult; + await lookupObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + const result = await getResult; - chai.expect(result.body).to.deep.equal(message.body); - chai.expect(result.attachments).to.deep.equal({}); - scope.done(); - }); + chai.expect(result.body).to.deep.equal(message.body); + chai.expect(result.attachments).to.deep.equal({}); + scope.done(); + }); - it('Gets a Document object with its attachment', async () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.lookupField = 'Id'; - testCommon.configuration.allowCriteriaToBeOmitted = false; - testCommon.configuration.allowZeroResults = false; - testCommon.configuration.passBinaryData = true; - - const message = { - body: { - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: '/upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'image/jpeg', - }, - }; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .times(2) - .reply(200, metaModelDocumentReply) - .get(message.body.Body) - .reply(200, JSON.stringify(message)) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) - .reply(200, { done: true, totalSize: 1, records: [message.body] }); - - nock(testCommon.EXT_FILE_STORAGE).put('', JSON.stringify(message)).reply(200); - - const getResult = new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') resolve(msg); + it('Gets a Document object with its attachment', async () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.lookupField = 'Id'; + testCommon.configuration.allowCriteriaToBeOmitted = false; + testCommon.configuration.allowZeroResults = false; + testCommon.configuration.passBinaryData = true; + + const message = { + body: { + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: '/upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'image/jpeg', + }, }; - }); + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .times(2) + .reply(200, metaModelDocumentReply) + .get(message.body.Body) + .reply(200, JSON.stringify(message)) + // eslint-disable-next-line max-len + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}%20ORDER%20BY%20LastModifiedDate%20ASC`) + .reply(200, { done: true, totalSize: 1, records: [message.body] }); + + nock(testCommon.EXT_FILE_STORAGE).put('/', JSON.stringify(message)).reply(200); + + const getResult = new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') resolve(msg); + }; + }); - await lookupObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - const result = await getResult; + await lookupObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + const result = await getResult; - chai.expect(result.body).to.deep.equal(message.body); - chai.expect(result.attachments).to.deep.equal({ - [`${message.body.Name}.jpeg`]: { - url: testCommon.EXT_FILE_STORAGE, - 'content-type': message.body.ContentType, - }, - }); + chai.expect(result.body).to.deep.equal(message.body); + chai.expect(result.attachments).to.deep.equal({ + [`${message.body.Name}.jpeg`]: { + url: testCommon.EXT_FILE_STORAGE, + 'content-type': message.body.ContentType, + }, + }); - scope.done(); + scope.done(); + }); }); }); diff --git a/spec/actions/lookupObjects.spec.js b/spec/actions/lookupObjects.spec.js index 0637511..4ef4cbb 100644 --- a/spec/actions/lookupObjects.spec.js +++ b/spec/actions/lookupObjects.spec.js @@ -4,89 +4,174 @@ const _ = require('lodash'); const common = require('../../lib/common.js'); const testCommon = require('../common.js'); -const objectTypesReply = require('../sfObjects.json'); -const metaModelDocumentReply = require('../sfDocumentMetadata.json'); -const metaModelAccountReply = require('../sfAccountMetadata.json'); +const objectTypesReply = require('../testData/sfObjects.json'); +const metaModelDocumentReply = require('../testData/sfDocumentMetadata.json'); +const metaModelAccountReply = require('../testData/sfAccountMetadata.json'); process.env.HASH_LIMIT_TIME = 1000; const lookupObjects = require('../../lib/actions/lookupObjects.js'); const COMPARISON_OPERATORS = ['=', '!=', '<', '<=', '>', '>=', 'LIKE', 'IN', 'NOT IN', 'INCLUDES', 'EXCLUDES']; -// Disable real HTTP requests -nock.disableNetConnect(); +describe('Lookup Objects action test', () => { + beforeEach(async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(10) + .reply(200, testCommon.secret); + }); + afterEach(() => { + nock.cleanAll(); + }); + describe('Lookup Objects module: objectTypes', () => { + it('Retrieves the list of queryable sobjects', async () => { + const scope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) + .reply(200, objectTypesReply); + + const expectedResult = {}; + objectTypesReply.sobjects.forEach((object) => { + if (object.queryable) expectedResult[object.name] = object.label; + }); -describe('Lookup Objects module: objectTypes', () => { - it('Retrieves the list of queryable sobjects', async () => { - const scope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) - .reply(200, objectTypesReply); + const result = await lookupObjects.objectTypes.call(testCommon, testCommon.configuration); + chai.expect(result).to.deep.equal(expectedResult); - const expectedResult = {}; - objectTypesReply.sobjects.forEach((object) => { - if (object.queryable) expectedResult[object.name] = object.label; + scope.done(); }); - - const result = await lookupObjects.objectTypes.call(testCommon, testCommon.configuration); - chai.expect(result).to.deep.equal(expectedResult); - - scope.done(); }); -}); -describe('Lookup Objects module: getMetaModel', () => { - function testMetaData(configuration, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${configuration.sobject}/describe`) - .reply(200, getMetaModelReply); - - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); - - const expectedResult = { - in: { - type: 'object', - properties: {}, - }, - out: { - type: 'object', - properties: { - results: { - type: 'array', - required: true, - properties: {}, + describe('Lookup Objects module: getMetaModel', () => { + function testMetaData(configuration, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${configuration.sobject}/describe`) + .reply(200, getMetaModelReply); + + const expectedResult = { + in: { + type: 'object', + properties: {}, + }, + out: { + type: 'object', + properties: { + results: { + type: 'array', + required: true, + properties: {}, + }, }, }, - }, - }; - - if (configuration.outputMethod === 'emitPage') { - expectedResult.in.properties.pageSize = { - title: 'Page size', - type: 'number', - required: false, - }; - expectedResult.in.properties.pageNumber = { - title: 'Page number', - type: 'number', - required: true, - }; - } else { - expectedResult.in.properties.limit = { - title: 'Maximum number of records', - type: 'number', - required: false, }; - } - const filterableFields = []; - getMetaModelReply.fields.forEach((field) => { - if (!field.deprecatedAndHidden) { - if (field.filterable && field.type !== 'address' && field.type !== 'location') { // Filter out compound fields - filterableFields.push(field.label); + if (configuration.outputMethod === 'emitPage') { + expectedResult.in.properties.pageSize = { + title: 'Page size', + type: 'number', + required: false, + }; + expectedResult.in.properties.pageNumber = { + title: 'Page number', + type: 'number', + required: true, + }; + } else { + expectedResult.in.properties.limit = { + title: 'Maximum number of records', + type: 'number', + required: false, + }; + } + + const filterableFields = []; + getMetaModelReply.fields.forEach((field) => { + if (!field.deprecatedAndHidden) { + if (field.filterable && field.type !== 'address' && field.type !== 'location') { // Filter out compound fields + filterableFields.push(field.label); + } + + const fieldDescriptor = { + title: field.label, + default: field.defaultValue, + type: (() => { + switch (field.soapType) { + case 'xsd:boolean': return 'boolean'; + case 'xsd:double': return 'number'; + case 'xsd:int': return 'number'; + case 'urn:address': return 'object'; + default: return 'string'; + } + })(), + required: !field.nillable && !field.defaultedOnCreate, + }; + + if (field.soapType === 'urn:address') { + fieldDescriptor.properties = { + city: { type: 'string' }, + country: { type: 'string' }, + postalCode: { type: 'string' }, + state: { type: 'string' }, + street: { type: 'string' }, + }; + } + + if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; + + if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { + fieldDescriptor.enum = []; + field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); + } + + if (configuration.lookupField === field.name) { + expectedResult.in.properties[field.name] = { + ...fieldDescriptor, required: !configuration.allowCriteriaToBeOmitted, + }; + } + + expectedResult.out.properties.results.properties[field.name] = fieldDescriptor; } + }); + + filterableFields.sort(); + + // eslint-disable-next-line no-plusplus + for (let i = 1; i <= configuration.termNumber; i += 1) { + expectedResult.in.properties[`sTerm_${i}`] = { + title: `Search term ${i}`, + type: 'object', + required: true, + properties: { + fieldName: { + title: 'Field name', + type: 'string', + required: true, + enum: filterableFields, + }, + condition: { + title: 'Condition', + type: 'string', + required: true, + enum: COMPARISON_OPERATORS, + }, + fieldValue: { + title: 'Field value', + type: 'string', + required: true, + }, + }, + }; + if (i !== parseInt(configuration.termNumber, 10)) { + expectedResult.in.properties[`link_${i}_${i + 1}`] = { + title: 'Logical operator', + type: 'string', + required: true, + enum: ['AND', 'OR'], + }; + } + } + + getMetaModelReply.fields.forEach((field) => { const fieldDescriptor = { title: field.label, default: field.defaultValue, @@ -126,780 +211,698 @@ describe('Lookup Objects module: getMetaModel', () => { } expectedResult.out.properties.results.properties[field.name] = fieldDescriptor; - } - }); + }); - filterableFields.sort(); - - // eslint-disable-next-line no-plusplus - for (let i = 1; i <= configuration.termNumber; i += 1) { - expectedResult.in.properties[`sTerm_${i}`] = { - title: `Search term ${i}`, - type: 'object', - required: true, - properties: { - fieldName: { - title: 'Field name', - type: 'string', - required: true, - enum: filterableFields, - }, - condition: { - title: 'Condition', - type: 'string', - required: true, - enum: COMPARISON_OPERATORS, + return lookupObjects.getMetaModel.call(testCommon, configuration) + .then((data) => { + chai.expect(data).to.deep.equal(expectedResult); + sfScope.done(); + }); + } + + it('Retrieves metadata for Document object', testMetaData.bind(null, { + ...testCommon.configuration, + sobject: 'Document', + outputMethod: 'emitAll', + termNumber: '1', + }, + metaModelDocumentReply)); + + it('Retrieves metadata for Document object 2 terms', testMetaData.bind(null, { + ...testCommon.configuration, + sobject: 'Document', + outputMethod: 'emitAll', + termNumber: '2', + }, + metaModelDocumentReply)); + + it('Retrieves metadata for Account object', testMetaData.bind(null, { + ...testCommon.configuration, + sobject: 'Account', + outputMethod: 'emitPage', + termNumber: '2', + }, + metaModelAccountReply)); + }); + + describe('Lookup Objects module: processAction', () => { + it('Gets Document objects: 2 string search terms, emitAll, limit', () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitAll'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + limit: 30, + sTerm_1: { + fieldName: 'Document Name', + fieldValue: 'NotVeryImportantDoc', + condition: '=', }, - fieldValue: { - title: 'Field value', - type: 'string', - required: true, + link_1_2: 'AND', + sTerm_2: { + fieldName: 'Folder ID', + fieldValue: 'Some folder ID', + condition: '=', }, }, }; - if (i !== parseInt(configuration.termNumber, 10)) { - expectedResult.in.properties[`link_${i}_${i + 1}`] = { - title: 'Logical operator', - type: 'string', - required: true, - enum: ['AND', 'OR'], - }; - } - } + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], + }; - getMetaModelReply.fields.forEach((field) => { - const fieldDescriptor = { - title: field.label, - default: field.defaultValue, - type: (() => { - switch (field.soapType) { - case 'xsd:boolean': return 'boolean'; - case 'xsd:double': return 'number'; - case 'xsd:int': return 'number'; - case 'urn:address': return 'object'; - default: return 'string'; + let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, + `Name%20%3D%20%27${message.body.sTerm_1.fieldValue}%27%20` + + `${message.body.link_1_2}%20` + + `FolderId%20%3D%20%27${message.body.sTerm_2.fieldValue}%27%20` + + `%20LIMIT%20${message.body.limit}`); + expectedQuery = expectedQuery.replace(/ /g, '%20'); + + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal(testReply); + scope.done(); + resolve(); } - })(), - required: !field.nillable && !field.defaultedOnCreate, + }; + }); + }); + + it('Gets Document objects: 2 string search terms, IN operator, emitAll, limit', () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitAll'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + limit: 30, + sTerm_1: { + fieldName: 'Document Name', + fieldValue: 'NotVeryImportantDoc,Value_1,Value_2', + condition: 'IN', + }, + link_1_2: 'AND', + sTerm_2: { + fieldName: 'Folder ID', + fieldValue: 'Some folder ID', + condition: '=', + }, + }, }; - if (field.soapType === 'urn:address') { - fieldDescriptor.properties = { - city: { type: 'string' }, - country: { type: 'string' }, - postalCode: { type: 'string' }, - state: { type: 'string' }, - street: { type: 'string' }, - }; - } + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], + }; - if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; + let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, + 'Name%20IN%20(%27NotVeryImportantDoc%27%2C%27Value_1%27%2C%27Value_2%27)%20' + + `${message.body.link_1_2}%20` + + `FolderId%20%3D%20%27${message.body.sTerm_2.fieldValue}%27%20` + + `%20LIMIT%20${message.body.limit}`); + expectedQuery = expectedQuery.replace(/ /g, '%20'); - if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { - fieldDescriptor.enum = []; - field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); - } + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - if (configuration.lookupField === field.name) { - expectedResult.in.properties[field.name] = { - ...fieldDescriptor, required: !configuration.allowCriteriaToBeOmitted, - }; - } + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - expectedResult.out.properties.results.properties[field.name] = fieldDescriptor; + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal(testReply); + scope.done(); + resolve(); + } + }; + }); }); - return lookupObjects.getMetaModel.call(testCommon, configuration) - .then((data) => { - chai.expect(data).to.deep.equal(expectedResult); - sfScope.done(); - }); - } - - it('Retrieves metadata for Document object', testMetaData.bind(null, { - ...testCommon.configuration, - sobject: 'Document', - outputMethod: 'emitAll', - termNumber: '1', - }, - metaModelDocumentReply)); - - it('Retrieves metadata for Document object 2 terms', testMetaData.bind(null, { - ...testCommon.configuration, - sobject: 'Document', - outputMethod: 'emitAll', - termNumber: '2', - }, - metaModelDocumentReply)); - - it('Retrieves metadata for Account object', testMetaData.bind(null, { - ...testCommon.configuration, - sobject: 'Account', - outputMethod: 'emitPage', - termNumber: '2', - }, - metaModelAccountReply)); -}); + it('Gets Document objects: 2 string search terms, NOT IN operator, emitAll, limit', () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitAll'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + limit: 30, + sTerm_1: { + fieldName: 'Body Length', + fieldValue: '32,12,234', + condition: 'NOT IN', + }, + link_1_2: 'AND', + sTerm_2: { + fieldName: 'Folder ID', + fieldValue: 'Some folder ID', + condition: '=', + }, + }, + }; -describe('Lookup Objects module: processAction', () => { - it('Gets Document objects: 2 string search terms, emitAll, limit', () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitAll'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - limit: 30, - sTerm_1: { - fieldName: 'Document Name', - fieldValue: 'NotVeryImportantDoc', - condition: '=', - }, - link_1_2: 'AND', - sTerm_2: { - fieldName: 'Folder ID', - fieldValue: 'Some folder ID', - condition: '=', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - ], - }; - - let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, - `Name%20%3D%20%27${message.body.sTerm_1.fieldValue}%27%20` - + `${message.body.link_1_2}%20` - + `FolderId%20%3D%20%27${message.body.sTerm_2.fieldValue}%27%20` - + `%20LIMIT%20${message.body.limit}`); - expectedQuery = expectedQuery.replace(/ /g, '%20'); - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal(testReply); - scope.done(); - resolve(); - } + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], }; - }); - }); - it('Gets Document objects: 2 string search terms, IN operator, emitAll, limit', () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitAll'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - limit: 30, - sTerm_1: { - fieldName: 'Document Name', - fieldValue: 'NotVeryImportantDoc,Value_1,Value_2', - condition: 'IN', - }, - link_1_2: 'AND', - sTerm_2: { - fieldName: 'Folder ID', - fieldValue: 'Some folder ID', - condition: '=', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - ], - }; - - let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, - 'Name%20IN%20(%27NotVeryImportantDoc%27%2C%27Value_1%27%2C%27Value_2%27)%20' + let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, + 'BodyLength%20NOT%20IN%20(32%2C12%2C234)%20' + `${message.body.link_1_2}%20` + `FolderId%20%3D%20%27${message.body.sTerm_2.fieldValue}%27%20` + `%20LIMIT%20${message.body.limit}`); - expectedQuery = expectedQuery.replace(/ /g, '%20'); - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal(testReply); - scope.done(); - resolve(); - } - }; + expectedQuery = expectedQuery.replace(/ /g, '%20'); + + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal(testReply); + scope.done(); + resolve(); + } + }; + }); }); - }); - it('Gets Document objects: 2 string search terms, NOT IN operator, emitAll, limit', () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitAll'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - limit: 30, - sTerm_1: { - fieldName: 'Body Length', - fieldValue: '32,12,234', - condition: 'NOT IN', - }, - link_1_2: 'AND', - sTerm_2: { - fieldName: 'Folder ID', - fieldValue: 'Some folder ID', - condition: '=', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - ], - }; - - let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, - 'BodyLength%20NOT%20IN%20(32%2C12%2C234)%20' - + `${message.body.link_1_2}%20` - + `FolderId%20%3D%20%27${message.body.sTerm_2.fieldValue}%27%20` - + `%20LIMIT%20${message.body.limit}`); - expectedQuery = expectedQuery.replace(/ /g, '%20'); - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal(testReply); - scope.done(); - resolve(); - } + it('Gets Document objects: 2 string search terms, INCLUDES operator, emitAll, limit', () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitAll'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + limit: 30, + sTerm_1: { + fieldName: 'Document Name', + fieldValue: 'NotVeryImportantDoc,Value_1,Value_2', + condition: 'INCLUDES', + }, + link_1_2: 'AND', + sTerm_2: { + fieldName: 'Folder ID', + fieldValue: 'Some folder ID', + condition: '=', + }, + }, }; - }); - }); - it('Gets Document objects: 2 string search terms, INCLUDES operator, emitAll, limit', () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitAll'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - limit: 30, - sTerm_1: { - fieldName: 'Document Name', - fieldValue: 'NotVeryImportantDoc,Value_1,Value_2', - condition: 'INCLUDES', - }, - link_1_2: 'AND', - sTerm_2: { - fieldName: 'Folder ID', - fieldValue: 'Some folder ID', - condition: '=', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - ], - }; - - let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, - 'Name%20INCLUDES%20(%27NotVeryImportantDoc%27%2C%27Value_1%27%2C%27Value_2%27)%20' + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], + }; + + let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, + 'Name%20INCLUDES%20(%27NotVeryImportantDoc%27%2C%27Value_1%27%2C%27Value_2%27)%20' + `${message.body.link_1_2}%20` + `FolderId%20%3D%20%27${message.body.sTerm_2.fieldValue}%27%20` + `%20LIMIT%20${message.body.limit}`); - expectedQuery = expectedQuery.replace(/ /g, '%20'); - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal(testReply); - scope.done(); - resolve(); - } - }; + expectedQuery = expectedQuery.replace(/ /g, '%20'); + + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal(testReply); + scope.done(); + resolve(); + } + }; + }); }); - }); - it('Gets Document objects: 2 string search terms, EXCLUDES operator, emitAll, limit', () => { - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitAll'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - limit: 30, - sTerm_1: { - fieldName: 'Body Length', - fieldValue: '32,12,234', - condition: 'EXCLUDES', - }, - link_1_2: 'AND', - sTerm_2: { - fieldName: 'Folder ID', - fieldValue: 'Some folder ID', - condition: '=', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - ], - }; - - let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, - 'BodyLength%20EXCLUDES%20(32%2C12%2C234)%20' + it('Gets Document objects: 2 string search terms, EXCLUDES operator, emitAll, limit', () => { + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitAll'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + limit: 30, + sTerm_1: { + fieldName: 'Body Length', + fieldValue: '32,12,234', + condition: 'EXCLUDES', + }, + link_1_2: 'AND', + sTerm_2: { + fieldName: 'Folder ID', + fieldValue: 'Some folder ID', + condition: '=', + }, + }, + }; + + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], + }; + + let expectedQuery = testCommon.buildSOQL(metaModelDocumentReply, + 'BodyLength%20EXCLUDES%20(32%2C12%2C234)%20' + `${message.body.link_1_2}%20` + `FolderId%20%3D%20%27${message.body.sTerm_2.fieldValue}%27%20` + `%20LIMIT%20${message.body.limit}`); - expectedQuery = expectedQuery.replace(/ /g, '%20'); - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal(testReply); - scope.done(); - resolve(); - } - }; + expectedQuery = expectedQuery.replace(/ /g, '%20'); + + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal(testReply); + scope.done(); + resolve(); + } + }; + }); }); - }); - it('Gets Account objects: 2 numeric search term, emitAll', () => { - testCommon.configuration.sobject = 'Account'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitAll'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - sTerm_1: { - fieldName: 'Employees', - fieldValue: '123', - condition: '=', - }, - link_1_2: 'OR', - sTerm_2: { - fieldName: 'Employees', - fieldValue: '1000', - condition: '>', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - ], - }; - - const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, - `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + it('Gets Account objects: 2 numeric search term, emitAll', () => { + testCommon.configuration.sobject = 'Account'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitAll'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + sTerm_1: { + fieldName: 'Employees', + fieldValue: '123', + condition: '=', + }, + link_1_2: 'OR', + sTerm_2: { + fieldName: 'Employees', + fieldValue: '1000', + condition: '>', + }, + }, + }; + + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], + }; + + const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, + `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + `${message.body.link_1_2}%20` + `NumberOfEmployees%20%3E%20${message.body.sTerm_2.fieldValue}%20` + '%20LIMIT%201000'); - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) - .reply(200, metaModelAccountReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal(testReply); - scope.done(); - resolve(); - } - }; + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) + .reply(200, metaModelAccountReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal(testReply); + scope.done(); + resolve(); + } + }; + }); }); - }); - it('Gets Account objects: 2 numeric search term, emitIndividually, limit = 2', () => { - testCommon.configuration.sobject = 'Account'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitIndividually'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - limit: 2, - sTerm_1: { - fieldName: 'Employees', - fieldValue: '123', - condition: '=', - }, - link_1_2: 'OR', - sTerm_2: { - fieldName: 'Employees', - fieldValue: '1000', - condition: '>', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - { - Id: 'tes12jId', - FolderId: '123sdfyyyzzz', - Name: 'sdfsdfg', - IsPublic: true, - Body: 'dfg.org', - ContentType: 'imagine/noHell', - }, - ], - }; - - const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, - `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + it('Gets Account objects: 2 numeric search term, emitIndividually, limit = 2', () => { + testCommon.configuration.sobject = 'Account'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitIndividually'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + limit: 2, + sTerm_1: { + fieldName: 'Employees', + fieldValue: '123', + condition: '=', + }, + link_1_2: 'OR', + sTerm_2: { + fieldName: 'Employees', + fieldValue: '1000', + condition: '>', + }, + }, + }; + + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + { + Id: 'tes12jId', + FolderId: '123sdfyyyzzz', + Name: 'sdfsdfg', + IsPublic: true, + Body: 'dfg.org', + ContentType: 'imagine/noHell', + }, + ], + }; + + const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, + `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + `${message.body.link_1_2}%20` + `NumberOfEmployees%20%3E%20${message.body.sTerm_2.fieldValue}%20` + `%20LIMIT%20${message.body.limit}`); - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) - .reply(200, metaModelAccountReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal({ results: [testReply.results.shift()] }); - if (testReply.results.length === 1) { - scope.done(); - resolve(); + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) + .reply(200, metaModelAccountReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal({ results: [testReply.results.shift()] }); + if (testReply.results.length === 1) { + scope.done(); + resolve(); + } } - } - }; + }; + }); }); - }); - it('Gets Account objects: 2 numeric search term, emitPage, pageNumber = 0, pageSize = 2, includeDeleted', () => { - testCommon.configuration.sobject = 'Account'; - testCommon.configuration.includeDeleted = true; - testCommon.configuration.outputMethod = 'emitPage'; - testCommon.configuration.termNumber = '2'; - - const message = { - body: { - pageNumber: 0, - pageSize: 2, - sTerm_1: { - fieldName: 'Employees', - fieldValue: '123', - condition: '=', - }, - link_1_2: 'OR', - sTerm_2: { - fieldName: 'Employees', - fieldValue: '1000', - condition: '>', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - { - Id: 'tes12jId', - FolderId: '123sdfyyyzzz', - Name: 'sdfsdfg', - IsPublic: true, - Body: 'dfg.org', - ContentType: 'imagine/noHell', - }, - { - Id: 't123es12jId', - FolderId: '123sdfysdfyyzzz', - Name: 'sdfsdfg', - IsPublic: true, - Body: 'dfg.osdfrg', - ContentType: 'imagine/nasdoHell', - }, - { - Id: 'zzzzzzz', - FolderId: '123sdfysdfyyzzz', - Name: 'sdfsdfg', - IsPublic: true, - Body: 'dfg.osdfrg', - ContentType: 'imagine/nasdoHell', - }, - ], - }; - - const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, - `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + it('Gets Account objects: 2 numeric search term, emitPage, pageNumber = 0, pageSize = 2, includeDeleted', () => { + testCommon.configuration.sobject = 'Account'; + testCommon.configuration.includeDeleted = true; + testCommon.configuration.outputMethod = 'emitPage'; + testCommon.configuration.termNumber = '2'; + + const message = { + body: { + pageNumber: 0, + pageSize: 2, + sTerm_1: { + fieldName: 'Employees', + fieldValue: '123', + condition: '=', + }, + link_1_2: 'OR', + sTerm_2: { + fieldName: 'Employees', + fieldValue: '1000', + condition: '>', + }, + }, + }; + + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + { + Id: 'tes12jId', + FolderId: '123sdfyyyzzz', + Name: 'sdfsdfg', + IsPublic: true, + Body: 'dfg.org', + ContentType: 'imagine/noHell', + }, + { + Id: 't123es12jId', + FolderId: '123sdfysdfyyzzz', + Name: 'sdfsdfg', + IsPublic: true, + Body: 'dfg.osdfrg', + ContentType: 'imagine/nasdoHell', + }, + { + Id: 'zzzzzzz', + FolderId: '123sdfysdfyyzzz', + Name: 'sdfsdfg', + IsPublic: true, + Body: 'dfg.osdfrg', + ContentType: 'imagine/nasdoHell', + }, + ], + }; + + const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, + `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + `${message.body.link_1_2}%20` + `NumberOfEmployees%20%3E%20${message.body.sTerm_2.fieldValue}%20` + `%20LIMIT%20${message.body.pageSize}`); - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) - .reply(200, metaModelAccountReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/queryAll?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal({ - results: [testReply.results[0], testReply.results[1]], - }); - scope.done(); - resolve(); - } - }; + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) + .reply(200, metaModelAccountReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/queryAll?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal({ + results: [testReply.results[0], testReply.results[1]], + }); + scope.done(); + resolve(); + } + }; + }); }); - }); - it('Gets Account objects: 3 search term, emitPage, pageNumber = 1, pageSize = 2', () => { - testCommon.configuration.sobject = 'Account'; - testCommon.configuration.includeDeleted = false; - testCommon.configuration.outputMethod = 'emitPage'; - testCommon.configuration.termNumber = '3'; - - const message = { - body: { - pageNumber: 1, - pageSize: 2, - sTerm_1: { - fieldName: 'Employees', - fieldValue: '123', - condition: '=', - }, - link_1_2: 'OR', - sTerm_2: { - fieldName: 'Employees', - fieldValue: '1000', - condition: '>', - }, - link_2_3: 'AND', - sTerm_3: { - fieldName: 'Account Name', - fieldValue: 'noname', - condition: 'LIKE', - }, - }, - }; - - const testReply = { - results: [{ - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - ContentType: 'imagine/noHeaven', - }, - { - Id: 'testObjId', - FolderId: '123yyyzzz', - Name: 'VeryImportantDoc', - IsPublic: true, - Body: 'wikipedia.org', - ContentType: 'imagine/noHell', - }, - { - Id: 'tes12jId', - FolderId: '123sdfyyyzzz', - Name: 'sdfsdfg', - IsPublic: true, - Body: 'dfg.org', - ContentType: 'imagine/noHell', - }, - { - Id: 't123es12jId', - FolderId: '123sdfysdfyyzzz', - Name: 'sdfsdfg', - IsPublic: true, - Body: 'dfg.osdfrg', - ContentType: 'imagine/nasdoHell', - }, - { - Id: 'zzzzzzz', - FolderId: '123sdfysdfyyzzz', - Name: 'sdfsdfg', - IsPublic: true, - Body: 'dfg.osdfrg', - ContentType: 'imagine/nasdoHell', - }, - ], - }; - - const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, - `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + it('Gets Account objects: 3 search term, emitPage, pageNumber = 1, pageSize = 2', () => { + testCommon.configuration.sobject = 'Account'; + testCommon.configuration.includeDeleted = false; + testCommon.configuration.outputMethod = 'emitPage'; + testCommon.configuration.termNumber = '3'; + + const message = { + body: { + pageNumber: 1, + pageSize: 2, + sTerm_1: { + fieldName: 'Employees', + fieldValue: '123', + condition: '=', + }, + link_1_2: 'OR', + sTerm_2: { + fieldName: 'Employees', + fieldValue: '1000', + condition: '>', + }, + link_2_3: 'AND', + sTerm_3: { + fieldName: 'Account Name', + fieldValue: 'noname', + condition: 'LIKE', + }, + }, + }; + + const testReply = { + results: [{ + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + { + Id: 'tes12jId', + FolderId: '123sdfyyyzzz', + Name: 'sdfsdfg', + IsPublic: true, + Body: 'dfg.org', + ContentType: 'imagine/noHell', + }, + { + Id: 't123es12jId', + FolderId: '123sdfysdfyyzzz', + Name: 'sdfsdfg', + IsPublic: true, + Body: 'dfg.osdfrg', + ContentType: 'imagine/nasdoHell', + }, + { + Id: 'zzzzzzz', + FolderId: '123sdfysdfyyzzz', + Name: 'sdfsdfg', + IsPublic: true, + Body: 'dfg.osdfrg', + ContentType: 'imagine/nasdoHell', + }, + ], + }; + + const expectedQuery = testCommon.buildSOQL(metaModelAccountReply, + `NumberOfEmployees%20%3D%20${message.body.sTerm_1.fieldValue}%20` + `${message.body.link_1_2}%20` + `NumberOfEmployees%20%3E%20${message.body.sTerm_2.fieldValue}%20` + `${message.body.link_2_3}%20` @@ -907,23 +910,24 @@ describe('Lookup Objects module: processAction', () => { + `%20LIMIT%20${message.body.pageSize}` + `%20OFFSET%20${message.body.pageSize}`); - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) - .reply(200, metaModelAccountReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) - .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); - - lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - return new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') { - chai.expect(msg.body).to.deep.equal({ - results: [testReply.results[0], testReply.results[1]], - }); - scope.done(); - resolve(); - } - }; + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Account/describe`) + .reply(200, metaModelAccountReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.results.length, records: testReply.results }); + + lookupObjects.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + return new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') { + chai.expect(msg.body).to.deep.equal({ + results: [testReply.results[0], testReply.results[1]], + }); + scope.done(); + resolve(); + } + }; + }); }); }); }); diff --git a/spec/actions/other.spec.js b/spec/actions/other.spec.js deleted file mode 100644 index 1cdf476..0000000 --- a/spec/actions/other.spec.js +++ /dev/null @@ -1,81 +0,0 @@ -const { expect } = require('chai'); -const sinon = require('sinon'); -const nock = require('nock'); -const logger = require('@elastic.io/component-logger')(); -const entry = require('../../lib/entry.js'); - -const { SalesforceEntity } = entry; -const oAuthUtils = require('../../lib/helpers/oauth-utils'); -const httpUtils = require('../../lib/helpers/http-utils'); - -describe('other Action Unit Test', () => { - const msg = {}; - const configuration = { - apiVersion: '39.0', - oauth: { - issued_at: '1541510572760', - token_type: 'Bearer', - id: 'https://login.salesforce.com/id/11/11', - instance_url: 'https://example.com', - id_token: 'ddd', - scope: 'refresh_token full', - signature: '=', - refresh_token: 'refresh_token', - access_token: 'access_token', - }, - object: 'Contact', - }; - - afterEach(() => { - sinon.restore(); - }); - - it('should emit three events', () => { - const emitter = { - emit: sinon.spy(), - logger, - }; - const entity = new SalesforceEntity(emitter); - const res = { - message: 'some message from example.com', - statusCode: 201, - }; - sinon.stub(oAuthUtils, 'refreshAppToken').callsFake((log, component, conf, next) => { - const refreshedCfg = conf; - refreshedCfg.oauth.access_token = 'aRefreshedToken'; - next(null, refreshedCfg); - }); - sinon.stub(httpUtils, 'getJSON').callsFake((log, params, next) => { - next(null, res); - }); - nock('https://example.com') - .post('/services/data/v45.0/sobjects/Contact') - .reply(res.statusCode, res); - - entity.processAction(configuration.object, msg, configuration); - expect(emitter.emit.withArgs('updateKeys').callCount).to.equal(1); - expect(emitter.emit.withArgs('data').callCount).to.equal(1); - expect(emitter.emit.withArgs('end').callCount).to.equal(1); - expect(emitter.emit.callCount).to.equal(3); - }); - - it('should emit an error', () => { - const emitter = { - emit: sinon.spy(), - logger, - }; - const entity = new SalesforceEntity(emitter); - const error = { - message: 'some error message from example.com', - statusCode: 404, - stack: 'some error stack', - }; - sinon.stub(oAuthUtils, 'refreshAppToken').callsFake((log, component, conf, next) => { - next(error); - }); - entity.processAction.call(emitter, configuration.object, msg, configuration); - expect(emitter.emit.withArgs('error').callCount).to.equal(1); - expect(emitter.emit.withArgs('end').callCount).to.equal(1); - expect(emitter.emit.callCount).to.equal(2); - }); -}); diff --git a/spec/actions/query.spec.js b/spec/actions/query.spec.js index 4749fde..bfd99fa 100644 --- a/spec/actions/query.spec.js +++ b/spec/actions/query.spec.js @@ -1,5 +1,3 @@ - - const chai = require('chai'); const nock = require('nock'); const sinon = require('sinon'); @@ -12,9 +10,6 @@ const testCommon = require('../common.js'); const queryObjects = require('../../lib/actions/query.js'); -// Disable real HTTP requests -nock.disableNetConnect(); - describe('Query module: processAction', () => { const context = { emit: sinon.spy(), logger }; @@ -38,70 +33,66 @@ describe('Query module: processAction', () => { }, ], }; + const message = { + body: { + query: 'select name, id from account where name = \'testtest\'', + }, + }; + const expectedQuery = 'select%20name%2C%20id%20from%20account%20where%20name%20%3D%20%27testtest%27'; + + beforeEach(() => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .reply(200, testCommon.secret); + }); afterEach(() => { + nock.cleanAll(); context.emit.resetHistory(); }); it('Gets objects not including deleted', async () => { - testCommon.configuration.includeDeleted = false; - testCommon.configuration.allowResultAsSet = true; - - const message = { - body: { - query: 'select name, id from account where name = \'testtest\'', - }, + const configuration = { + ...testCommon.configuration, + includeDeleted: false, + allowResultAsSet: true, }; - - const expectedQuery = 'select%20name%2C%20id%20from%20account%20where%20name%20%3D%20%27testtest%27'; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) .reply(200, { done: true, totalSize: testReply.result.length, records: testReply.result }); - await queryObjects.process.call(context, message, testCommon.configuration); + await queryObjects.process.call(context, message, configuration); scope.done(); expect(context.emit.getCall(0).lastArg.body).to.deep.equal(testReply); }); it('Gets objects including deleted', async () => { - testCommon.configuration.includeDeleted = true; - testCommon.configuration.allowResultAsSet = true; - - const message = { - body: { - query: 'select name, id from account where name = \'testtest\'', - }, + const configuration = { + ...testCommon.configuration, + includeDeleted: true, + allowResultAsSet: true, }; - - const expectedQuery = 'select%20name%2C%20id%20from%20account%20where%20name%20%3D%20%27testtest%27'; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/queryAll?q=${expectedQuery}`) .reply(200, { done: true, totalSize: testReply.result.length, records: testReply.result }); - await queryObjects.process.call(context, message, testCommon.configuration); + await queryObjects.process.call(context, message, configuration); scope.done(); expect(context.emit.getCall(0).lastArg.body).to.deep.equal(testReply); }); it('Gets objects batchSize=1, allowResultAsSet = false', async () => { - testCommon.configuration.includeDeleted = true; - testCommon.configuration.allowResultAsSet = false; - testCommon.configuration.batchSize = 1; - - const message = { - body: { - query: 'select name, id from account where name = \'testtest\'', - }, + const configuration = { + ...testCommon.configuration, + includeDeleted: true, + allowResultAsSet: false, + batchSize: 1, }; - const expectedQuery = 'select%20name%2C%20id%20from%20account%20where%20name%20%3D%20%27testtest%27'; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/queryAll?q=${expectedQuery}`) .reply(200, { done: true, totalSize: testReply.result.length, records: testReply.result }); - await queryObjects.process.call(context, message, testCommon.configuration); + await queryObjects.process.call(context, message, configuration); scope.done(); expect(context.emit.getCalls().length).to.be.equal(2); expect(context.emit.getCall(0).lastArg.body).to.deep.equal({ result: [testReply.result[0]] }); @@ -109,39 +100,33 @@ describe('Query module: processAction', () => { }); it('Gets objects batchSize=1, allowResultAsSet = true', async () => { - testCommon.configuration.includeDeleted = true; - testCommon.configuration.allowResultAsSet = true; - const message = { - body: { - query: 'select name, id from account where name = \'testtest\'', - }, + const configuration = { + ...testCommon.configuration, + includeDeleted: true, + allowResultAsSet: true, }; - const expectedQuery = 'select%20name%2C%20id%20from%20account%20where%20name%20%3D%20%27testtest%27'; - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/queryAll?q=${expectedQuery}`) .reply(200, { done: true, totalSize: testReply.result.length, records: testReply.result }); - await queryObjects.process.call(context, message, testCommon.configuration); + await queryObjects.process.call(context, message, configuration); scope.done(); expect(context.emit.getCalls().length).to.be.equal(1); expect(context.emit.getCall(0).lastArg.body).to.deep.equal(testReply); }); it('Gets objects batchSize=0, allowResultAsSet = false', async () => { - testCommon.configuration.includeDeleted = true; - testCommon.configuration.allowResultAsSet = undefined; - testCommon.configuration.batchSize = 0; - const message = { - body: { - query: 'select name, id from account where name = \'testtest\'', - }, + const configuration = { + ...testCommon.configuration, + includeDeleted: true, + allowResultAsSet: undefined, + batchSize: 0, }; - const expectedQuery = 'select%20name%2C%20id%20from%20account%20where%20name%20%3D%20%27testtest%27'; - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/queryAll?q=${expectedQuery}`) .reply(200, { done: true, totalSize: testReply.result.length, records: testReply.result }); - await queryObjects.process.call(context, message, testCommon.configuration); + await queryObjects.process.call(context, message, configuration); scope.done(); expect(context.emit.getCalls().length).to.be.equal(2); expect(context.emit.getCall(0).lastArg.body).to.deep.equal(testReply.result[0]); diff --git a/spec/actions/upsert.spec.js b/spec/actions/upsert.spec.js index 0a54d58..7ba21ab 100644 --- a/spec/actions/upsert.spec.js +++ b/spec/actions/upsert.spec.js @@ -1,199 +1,201 @@ - - +/* eslint-disable max-len */ const chai = require('chai'); const nock = require('nock'); const _ = require('lodash'); const common = require('../../lib/common.js'); const testCommon = require('../common.js'); -const objectTypesReply = require('../sfObjects.json'); -const metaModelDocumentReply = require('../sfDocumentMetadata.json'); -const metaModelAccountReply = require('../sfAccountMetadata.json'); +const objectTypesReply = require('../testData/sfObjects.json'); +const metaModelDocumentReply = require('../testData/sfDocumentMetadata.json'); +const metaModelAccountReply = require('../testData/sfAccountMetadata.json'); const upsertObject = require('../../lib/actions/upsert.js'); -// Disable real HTTP requests -nock.disableNetConnect(); +describe('Upsert Object test', () => { + beforeEach(() => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(3) + .reply(200, testCommon.secret); + }); + afterEach(() => { + nock.cleanAll(); + }); + describe('Upsert Object module: objectTypes', () => { + it('Retrieves the list of createable/updateable sobjects', async () => { + const scope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) + .reply(200, objectTypesReply); + + const expectedResult = {}; + objectTypesReply.sobjects.forEach((object) => { + if (object.createable && object.updateable) expectedResult[object.name] = object.label; + }); -describe('Upsert Object module: objectTypes', () => { - it('Retrieves the list of createable/updateable sobjects', async () => { - const scope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) - .reply(200, objectTypesReply); + const result = await upsertObject.objectTypes.call(testCommon, testCommon.configuration); + chai.expect(result).to.deep.equal(expectedResult); - const expectedResult = {}; - objectTypesReply.sobjects.forEach((object) => { - if (object.createable && object.updateable) expectedResult[object.name] = object.label; + scope.done(); }); - - const result = await upsertObject.objectTypes.call(testCommon, testCommon.configuration); - chai.expect(result).to.deep.equal(expectedResult); - - scope.done(); }); -}); - -describe('Upsert Object module: getMetaModel', () => { - function testMetaData(object, getMetaModelReply) { - const sfScope = nock(testCommon.configuration.oauth.instance_url) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) - .reply(200, getMetaModelReply); - - nock(testCommon.refresh_token.url) - .post('') - .reply(200, testCommon.refresh_token.response); - - const expectedResult = { - in: { - type: 'object', - properties: {}, - }, - out: { - type: 'object', - properties: {}, - }, - }; - getMetaModelReply.fields.forEach((field) => { - if (field.createable) { - const fieldDescriptor = { - title: field.label, - default: field.defaultValue, - type: (() => { - switch (field.soapType) { - case 'xsd:boolean': return 'boolean'; - case 'xsd:double': return 'number'; - case 'xsd:int': return 'number'; - default: return 'string'; - } - })(), - required: !field.nillable && !field.defaultedOnCreate, - }; - if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; + describe('Upsert Object module: getMetaModel', () => { + function testMetaData(object, getMetaModelReply) { + const sfScope = nock(testCommon.instanceUrl) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/${object}/describe`) + .reply(200, getMetaModelReply); - if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { - fieldDescriptor.enum = []; - field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); + const expectedResult = { + in: { + type: 'object', + properties: {}, + }, + out: { + type: 'object', + properties: {}, + }, + }; + getMetaModelReply.fields.forEach((field) => { + if (field.createable) { + const fieldDescriptor = { + title: field.label, + default: field.defaultValue, + type: (() => { + switch (field.soapType) { + case 'xsd:boolean': return 'boolean'; + case 'xsd:double': return 'number'; + case 'xsd:int': return 'number'; + default: return 'string'; + } + })(), + required: !field.nillable && !field.defaultedOnCreate, + }; + + if (field.type === 'textarea') fieldDescriptor.maxLength = 1000; + + if (field.picklistValues !== undefined && field.picklistValues.length !== 0) { + fieldDescriptor.enum = []; + field.picklistValues.forEach((pick) => { fieldDescriptor.enum.push(pick.value); }); + } + + expectedResult.in.properties[field.name] = { ...fieldDescriptor, required: false }; + expectedResult.out.properties[field.name] = fieldDescriptor; } + }); - expectedResult.in.properties[field.name] = { ...fieldDescriptor, required: false }; - expectedResult.out.properties[field.name] = fieldDescriptor; - } - }); + expectedResult.in.properties.Id = { + type: 'string', + required: false, + title: 'Id', + }; - expectedResult.in.properties.Id = { - type: 'string', - required: false, - title: 'Id', - }; - - testCommon.configuration.sobject = object; - return upsertObject.getMetaModel.call(testCommon, testCommon.configuration) - .then((data) => { - chai.expect(data).to.deep.equal(expectedResult); - sfScope.done(); - }); - } + testCommon.configuration.sobject = object; + return upsertObject.getMetaModel.call(testCommon, testCommon.configuration) + .then((data) => { + chai.expect(data).to.deep.equal(expectedResult); + sfScope.done(); + }); + } - it('Retrieves metadata for Document object', testMetaData.bind(null, 'Document', metaModelDocumentReply)); - it('Retrieves metadata for Account object', testMetaData.bind(null, 'Account', metaModelAccountReply)); -}); + it('Retrieves metadata for Document object', testMetaData.bind(null, 'Document', metaModelDocumentReply)); + it('Retrieves metadata for Account object', testMetaData.bind(null, 'Account', metaModelAccountReply)); + }); -describe('Upsert Object module: upsertObject', () => { - it('Sends request for Document update not using input attachment', async () => { - const message = { - body: { - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'not quite binary data', - ContentType: 'application/octet-stream', - }, - attachments: { - theFile: { - url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - 'content-type': 'image/jpeg', + describe('Upsert Object module: upsertObject', () => { + it('Sends request for Document update not using input attachment', async () => { + const message = { + body: { + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'not quite binary data', + ContentType: 'application/octet-stream', + }, + attachments: { + theFile: { + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + 'content-type': 'image/jpeg', + }, }, - }, - }; - - const resultRequestBody = _.cloneDeep(message.body); - delete resultRequestBody.Id; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .patch(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/${message.body.Id}`, resultRequestBody) - .reply(204) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) - .reply(200, { done: true, totalSize: 1, records: [message.body] }); - - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.utilizeAttachment = false; - - const getResult = new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') resolve(msg); }; - }); - await upsertObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - const result = await getResult; + const resultRequestBody = _.cloneDeep(message.body); + delete resultRequestBody.Id; - chai.expect(result.body).to.deep.equal(message.body); - scope.done(); - }); + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .patch(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/${message.body.Id}`, resultRequestBody) + .reply(204) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) + .reply(200, { done: true, totalSize: 1, records: [message.body] }); + + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.utilizeAttachment = false; + + const getResult = new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') resolve(msg); + }; + }); + + await upsertObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + const result = await getResult; - it('Sends request for Document update using input attachment', async () => { - const message = { - body: { - Id: 'testObjId', - FolderId: 'xxxyyyzzz', - Name: 'NotVeryImportantDoc', - IsPublic: false, - Body: 'not quite binary data', - ContentType: 'application/octet-stream', - }, - attachments: { - theFile: { - url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', - 'content-type': 'image/jpeg', + chai.expect(result.body).to.deep.equal(message.body); + scope.done(); + }); + + it('Sends request for Document update using input attachment', async () => { + const message = { + body: { + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'not quite binary data', + ContentType: 'application/octet-stream', + }, + attachments: { + theFile: { + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + 'content-type': 'image/jpeg', + }, }, - }, - }; - - const resultRequestBody = _.cloneDeep(message.body); - delete resultRequestBody.Id; - resultRequestBody.Body = Buffer.from(JSON.stringify(message)).toString('base64'); // Take the message as binary data - resultRequestBody.ContentType = message.attachments.theFile['content-type']; - - const scope = nock(testCommon.configuration.oauth.instance_url, { encodedQueryParams: true }) - .patch(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/${message.body.Id}`, resultRequestBody) - .reply(204) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) - .times(2) - .reply(200, metaModelDocumentReply) - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) - .reply(200, { done: true, totalSize: 1, records: [message.body] }); - - const binaryScope = nock('https://upload.wikimedia.org') - .get('/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg') - .reply(200, JSON.stringify(message)); - - testCommon.configuration.sobject = 'Document'; - testCommon.configuration.utilizeAttachment = true; - - const getResult = new Promise((resolve) => { - testCommon.emitCallback = (what, msg) => { - if (what === 'data') resolve(msg); }; - }); - await upsertObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); - const result = await getResult; + const resultRequestBody = _.cloneDeep(message.body); + delete resultRequestBody.Id; + resultRequestBody.Body = Buffer.from(JSON.stringify(message)).toString('base64'); // Take the message as binary data + resultRequestBody.ContentType = message.attachments.theFile['content-type']; + + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .patch(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/${message.body.Id}`, resultRequestBody) + .reply(204) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, metaModelDocumentReply) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${testCommon.buildSOQL(metaModelDocumentReply, { Id: message.body.Id })}`) + .reply(200, { done: true, totalSize: 1, records: [message.body] }); + + const binaryScope = nock('https://upload.wikimedia.org') + .get('/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg') + .reply(200, JSON.stringify(message)); + + testCommon.configuration.sobject = 'Document'; + testCommon.configuration.utilizeAttachment = true; + + const getResult = new Promise((resolve) => { + testCommon.emitCallback = (what, msg) => { + if (what === 'data') resolve(msg); + }; + }); - chai.expect(result.body).to.deep.equal(message.body); - scope.done(); - binaryScope.done(); + await upsertObject.process.call(testCommon, _.cloneDeep(message), testCommon.configuration); + const result = await getResult; + + chai.expect(result.body).to.deep.equal(message.body); + scope.done(); + binaryScope.done(); + }); }); }); diff --git a/spec/common.js b/spec/common.js index c3edbc9..def78f8 100644 --- a/spec/common.js +++ b/spec/common.js @@ -2,8 +2,13 @@ require('elasticio-rest-node'); process.env.OAUTH_CLIENT_ID = 'asd'; process.env.OAUTH_CLIENT_SECRET = 'sdc'; +process.env.ELASTICIO_API_URI = 'https://app.example.io'; +process.env.ELASTICIO_API_USERNAME = 'user'; +process.env.ELASTICIO_API_KEY = 'apiKey'; +process.env.ELASTICIO_WORKSPACE_ID = 'workspaceId'; -const EXT_FILE_STORAGE = 'http://file.storage.server/file'; +const EXT_FILE_STORAGE = 'http://file.storage.server'; +const instanceUrl = 'https://test.salesforce.com'; require.cache[require.resolve('elasticio-rest-node')] = { exports: () => ({ @@ -20,17 +25,20 @@ require.cache[require.resolve('elasticio-rest-node')] = { module.exports = { configuration: { - prodEnv: 'login', - oauth: { - access_token: 'the unthinkable top secret access token', - refresh_token: 'the not less important also unthinkable top secret refresh token', - signature: 'one of that posh signatures that is far from your cross', - scope: 'refresh_token full', - id_token: 'yqwdovsdfuf34fmvsdargbnr43a23egc14em8hdfy4tpe8ovq8rvshexdtartdthis', - instance_url: 'https://test.salesforce.com', - id: 'https://login.salesforce.com/id/000ZqVZEA0/0052o000ekAAA', - token_type: 'Bearer', - issued_at: '1566325368430', + secretId: 'secretId', + }, + secretId: 'secretId', + instanceUrl, + secret: { + data: { + attributes: { + credentials: { + access_token: 'accessToken', + undefined_params: { + instance_url: instanceUrl, + }, + }, + }, }, }, refresh_token: { @@ -71,7 +79,7 @@ module.exports = { // eslint-disable-next-line guard-for-in,no-restricted-syntax for (const key in where) { soql += `${key}%20%3D%20`; - const field = objectMeta.fields.find(f => f.name === key); + const field = objectMeta.fields.find((f) => f.name === key); if (!field) { throw new Error(`There is not ${key} field in ${objectMeta.name} object`); } diff --git a/spec/entry.spec.js b/spec/entry.spec.js deleted file mode 100644 index 6c11cab..0000000 --- a/spec/entry.spec.js +++ /dev/null @@ -1,149 +0,0 @@ -const nock = require('nock'); -const sinon = require('sinon'); -const chai = require('chai'); -const logger = require('@elastic.io/component-logger')(); -const entry = require('../lib/entry.js'); -const objectDescription = require('./testData/objectDescriptionForMetadata'); -const objectFullDescription = require('./testData/objectDescription'); -const expectedMetadataOut = require('./testData/expectedMetadataOut'); -const objectsList = require('./testData/objectsList'); -const oAuthUtils = require('../lib/helpers/oauth-utils.js'); -const common = require('../lib/common.js'); - -const { expect } = chai; -let emitter; - -describe('Test entry', () => { - beforeEach(() => { - emitter = { - emit: sinon.spy(), - logger, - }; - sinon.stub(entry, 'SalesforceEntity').callsFake(() => new entry.SalesforceEntity(emitter)); - sinon.stub(oAuthUtils, 'refreshAppToken').callsFake((log, component, conf, next) => { - const refreshedCfg = conf; - refreshedCfg.oauth.access_token = 'aRefreshedToken'; - next(null, refreshedCfg); - }); - }); - - afterEach(() => { - sinon.restore(); - }); - - describe('Test getMetaModel', () => { - it('Get Out Metadata, other entity type', (done) => { - nock('http://localhost:1234') - .matchHeader('Authorization', 'Bearer aRefreshedToken') - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Event/describe`) - .reply(200, JSON.stringify(objectDescription)); - - const cfg = { - sobject: 'Event', - oauth: { - instance_url: 'http://localhost:1234', - access_token: 'aToken', - }, - }; - entry.getMetaModel.call(emitter, cfg, (error, result) => { - if (error) return done(error); - try { - expect(error).to.equal(null); - expect(result).to.deep.equal({ out: expectedMetadataOut }); - expect(emitter.emit.withArgs('updateKeys').callCount).to.be.equal(1); - return done(); - } catch (e) { - return done(e); - } - }); - }); - }); - - describe('Test objectTypes', () => { - it('should return object types', (done) => { - nock('http://localhost:1234') - .matchHeader('Authorization', 'Bearer aRefreshedToken') - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) - .reply(200, JSON.stringify(objectsList)); - - const cfg = { - object: 'Event', - oauth: { - instance_url: 'http://localhost:1234', - access_token: 'aToken', - }, - }; - entry.objectTypes.call(emitter, cfg, (error, result) => { - if (error) return done(error); - try { - expect(error).to.equal(null); - expect(result).to.deep.equal({ Account: 'Account', AccountContactRole: 'Account Contact Role' }); - expect(emitter.emit.withArgs('updateKeys').callCount).to.be.equal(1); - return done(); - } catch (e) { - return done(e); - } - }); - }); - }); - - describe('Test linkedObjectTypes', () => { - it('should return linked object types', (done) => { - nock('http://localhost:1234') - .matchHeader('Authorization', 'Bearer aRefreshedToken') - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Event/describe`) - .reply(200, objectFullDescription); - - const cfg = { - object: 'Event', - oauth: { - instance_url: 'http://localhost:1234', - access_token: 'aToken', - }, - }; - - const expectedResult = { - MasterRecord: 'Contact (MasterRecord)', - Account: 'Account (Account)', - ReportsTo: 'Contact (ReportsTo)', - Owner: 'User (Owner)', - CreatedBy: 'User (CreatedBy)', - LastModifiedBy: 'User (LastModifiedBy)', - '!AccountContactRoles': 'AccountContactRole (AccountContactRoles)', - '!ActivityHistories': 'ActivityHistory (ActivityHistories)', - '!Assets': 'Asset (Assets)', - '!Attachments': 'Attachment (Attachments)', - '!CampaignMembers': 'CampaignMember (CampaignMembers)', - '!Cases': 'Case (Cases)', - '!CaseContactRoles': 'CaseContactRole (CaseContactRoles)', - '!Feeds': 'ContactFeed (Feeds)', - '!Histories': 'ContactHistory (Histories)', - '!Shares': 'ContactShare (Shares)', - '!ContractsSigned': 'Contract (ContractsSigned)', - '!ContractContactRoles': 'ContractContactRole (ContractContactRoles)', - '!EmailStatuses': 'EmailStatus (EmailStatuses)', - '!FeedSubscriptionsForEntity': 'EntitySubscription (FeedSubscriptionsForEntity)', - '!Events': 'Event (Events)', - '!Notes': 'Note (Notes)', - '!NotesAndAttachments': 'NoteAndAttachment (NotesAndAttachments)', - '!OpenActivities': 'OpenActivity (OpenActivities)', - '!OpportunityContactRoles': 'OpportunityContactRole (OpportunityContactRoles)', - '!ProcessInstances': 'ProcessInstance (ProcessInstances)', - '!ProcessSteps': 'ProcessInstanceHistory (ProcessSteps)', - '!Tasks': 'Task (Tasks)', - }; - - entry.linkedObjectTypes.call(emitter, cfg, (error, result) => { - if (error) return done(error); - try { - expect(error).to.equal(null); - expect(result).to.deep.equal(expectedResult); - expect(emitter.emit.withArgs('updateKeys').callCount).to.be.equal(1); - return done(); - } catch (e) { - return done(e); - } - }); - }); - }); -}); diff --git a/spec/helpers/attachment.spec.js b/spec/helpers/attachment.spec.js new file mode 100644 index 0000000..41ce277 --- /dev/null +++ b/spec/helpers/attachment.spec.js @@ -0,0 +1,93 @@ +const { expect } = require('chai'); +const nock = require('nock'); +const logger = require('@elastic.io/component-logger')(); +const common = require('../../lib/common.js'); +const testCommon = require('../common.js'); +const { prepareBinaryData, getAttachment } = require('../../lib/helpers/attachment'); + +describe('attachment helper', () => { + beforeEach(() => { + nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .reply(200, { + fields: [ + { + name: 'Body', + type: 'base64', + }, + { + name: 'ContentType', + }, + ], + }); + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(2) + .reply(200, testCommon.secret); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + const configuration = { + secretId: testCommon.secretId, + sobject: 'Document', + }; + + describe('prepareBinaryData test', () => { + it('should upload attachment utilizeAttachment:true', async () => { + const msg = { + body: { + Name: 'Attachment', + }, + attachments: { + 'Fox.jpeg': { + 'content-type': 'image/jpeg', + size: 126564, + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + }, + }, + }; + await prepareBinaryData(msg, { ...configuration, utilizeAttachment: true }, { logger }); + expect(msg.body.Name).to.eql('Attachment'); + expect(msg.body.ContentType).to.eql('image/jpeg'); + expect(Object.prototype.hasOwnProperty.call(msg.body, 'Body')).to.eql(true); + }); + + it('should discard attachment utilizeAttachment:false', async () => { + const msg = { + body: { + Name: 'Without Attachment', + }, + attachments: { + 'Fox.jpeg': { + 'content-type': 'image/jpeg', + size: 126564, + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + }, + }, + }; + await prepareBinaryData(msg, configuration, { logger }); + expect(msg.body.Name).to.eql('Without Attachment'); + expect(Object.prototype.hasOwnProperty.call(msg.body, 'Body')).to.eql(false); + }); + }); + + describe.skip('getAttachment test', async () => { + it('should getAttachment', async () => { + nock(testCommon.instanceUrl) + .get('/services/data/v46.0/sobjects/Attachment/00P2R00001DYjNVUA1/Body') + .reply(200, { hello: 'world' }); + const objectContent = { + Body: '/services/data/v46.0/sobjects/Attachment/00P2R00001DYjNVUA1/Body', + }; + const result = await getAttachment(configuration, objectContent, { logger }); + expect(result).to.eql({ + attachment: { + url: 'http://file.storage.server', + }, + }); + }); + }); +}); diff --git a/spec/helpers/describe.spec.js b/spec/helpers/describe.spec.js deleted file mode 100644 index 1837f6d..0000000 --- a/spec/helpers/describe.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -const nock = require('nock'); -const chai = require('chai'); -const logger = require('@elastic.io/component-logger')(); - -const { expect } = chai; -const { describeObject, fetchObjectTypes } = require('../../lib/helpers/describe'); -const description = require('../testData/objectDescription.json'); -const objectLists = require('../testData/objectsList.json'); -const common = require('../../lib/common.js'); - -const cfg = { - oauth: { - access_token: '00DE0000000dwKc!ARcAQEgJMHustyszwbrh06CGCuYPn2aI..bAV4T8aA8aDXRpAWeveWq7jhUlGl7d2e7T8itqCX1F0f_LeuMDiGzZrdOIIVnE', - refresh_token: '5Aep861rEpScxnNE66jGO6gqeJ82V9qXOs5YIxlkVZgWYMSJfjLeqYUwKNfA2R7cU04EyjVKE9_A.vqQY9kjgUg', - instance_url: 'https://na9.salesforce.com', - }, - sobject: 'Contact', - apiVersion: `v${common.globalConsts.SALESFORCE_API_VERSION}`, -}; - -describe('Describe helper', () => { - it('describeObject should fetch a description for the specified object type', async () => { - nock('https://na9.salesforce.com') - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Contact/describe`) - .reply(200, JSON.stringify(description)); - - const result = await describeObject(logger, { cfg }); - expect(result).to.deep.equal(description); - }); - - it('should fetch a description for the specified object type', (done) => { - nock('https://na9.salesforce.com') - .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) - .reply(200, objectLists); - - fetchObjectTypes(logger, cfg, (err, result) => { - try { - expect(err).to.equal(null); - expect(result).to.deep.equal({ - Account: 'Account', - AccountContactRole: 'Account Contact Role', - }); - done(); - } catch (e) { - done(e); - } - }); - }); -}); diff --git a/spec/helpers/error.spec.js b/spec/helpers/error.spec.js deleted file mode 100644 index 8700490..0000000 --- a/spec/helpers/error.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -const chai = require('chai'); - -const { expect } = chai; -const createPresentableError = require('../../lib/helpers/error.js'); - -describe('Error creation', () => { - describe('given an error object with respnseBody and statusCode property', () => { - it('should return an error object with additional properties: [view: {textKey, defaultText}, statusCode]', () => { - const input = new Error('Some error message'); - input.responseBody = '{"message":"The REST API is not enabled for this Organization.","errorCode":"API_DISABLED_FOR_ORG"}'; - input.statusCode = 403; - - const error = createPresentableError(input); - - expect(error.statusCode).to.equal(403); - expect(error.view).to.deep.equal({ - textKey: 'salesforce_API_DISABLED_FOR_ORG', - defaultText: 'The REST API is not enabled for this Organization.', - }); - }); - }); - - describe('given an error object with array in responseBody', () => { - it('should return an error object prepared for view, from the first member of the array', () => { - const input = new Error('Some error message'); - input.responseBody = '[{"message":"The REST API is not enabled for this Organization.","errorCode":"API_DISABLED_FOR_ORG"}]'; - input.statusCode = 403; - - const error = createPresentableError(input); - - expect(error.statusCode).to.equal(403); - expect(error.view).to.deep.equal({ - textKey: 'salesforce_API_DISABLED_FOR_ORG', - defaultText: 'The REST API is not enabled for this Organization.', - }); - }); - }); - - describe('given an error with unparsable responseBody', () => { - it('should return null', () => { - const input = new Error('Some error message'); - input.responseBody = {}; - const error = createPresentableError(input); - - expect(error).to.equal(null); - }); - }); -}); diff --git a/spec/helpers/http-utils.spec.js b/spec/helpers/http-utils.spec.js deleted file mode 100644 index 8ee62a8..0000000 --- a/spec/helpers/http-utils.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -const nock = require('nock'); -const chai = require('chai'); -const logger = require('@elastic.io/component-logger')(); - -const { expect } = chai; -const action = require('../../lib/helpers/http-utils'); - -describe('http-utils Unit Test', () => { - it('Should pass', async () => { - const params = { - url: 'https://testurl.com/testing', - method: 'get', - headers: {}, - statusExpected: 200, - }; - const body = '{"testMessage": "Pass test message"}'; - nock('https://testurl.com') - .get('/testing') - .reply(200, body); - - action.getJSON(logger, params, (error, result) => { - expect(result.testMessage).to.equal('Pass test message'); - }); - }); - - it('Should throw error', () => { - const params = { - url: 'https://testurl.com/testing', - method: 'get', - headers: {}, - statusExpected: 200, - }; - const body = '{"testMessage": "Error test message"}'; - nock('https://testurl.com') - .get('/testing') - .reply(404, body); - - action.getJSON(logger, params, (error) => { - expect(error.responseBody).to.equal(body); - expect(error.statusCode).to.equal(404); - }); - }); -}); diff --git a/spec/helpers/lookupCache.spec.js b/spec/helpers/lookupCache.spec.js index fa47be5..6ef1909 100644 --- a/spec/helpers/lookupCache.spec.js +++ b/spec/helpers/lookupCache.spec.js @@ -3,145 +3,147 @@ const chai = require('chai'); process.env.HASH_LIMIT_TIME = 1000; const { lookupCache } = require('../../lib/helpers/lookupCache.js'); -describe('Lookup Cache class unit tests', () => { - before(() => { - lookupCache.clear(); +describe('Lookup Cache unit tests', () => { + describe('Lookup Cache class unit tests', () => { + before(() => { + lookupCache.clear(); + }); + + beforeEach(() => { + lookupCache.enableCache(); + }); + + afterEach(() => { + lookupCache.clear(); + }); + + it('getMap', async () => { + const map = lookupCache.getMap(); + chai.expect(map).to.equal(lookupCache.requestCache); + }); + + it('hasKey', async () => { + const map = lookupCache.getMap(); + + map.set('a', { letter: 'a' }); + map.set('b', { letter: 'b' }); + map.set('c', { letter: 'c' }); + + chai.expect(lookupCache.hasKey('a')).to.equal(true); + chai.expect(lookupCache.hasKey('b')).to.equal(true); + chai.expect(lookupCache.hasKey('c')).to.equal(true); + chai.expect(lookupCache.hasKey('d')).to.equal(false); + }); + + it('addRequestResponsePair', async () => { + lookupCache.addRequestResponsePair('a', { letter: 'a' }); + lookupCache.addRequestResponsePair('b', { letter: 'b' }); + lookupCache.addRequestResponsePair('c', { letter: 'c' }); + + const map = lookupCache.getMap(); + + chai.expect(map.zeroNode.nextNode.key).to.equal('c'); + chai.expect(map.zeroNode.nextNode.value.letter).to.equal('c'); + chai.expect(map.zeroNode.nextNode.nextNode.key).to.equal('b'); + chai.expect(map.zeroNode.nextNode.nextNode.value.letter).to.equal('b'); + chai.expect(map.zeroNode.nextNode.nextNode.nextNode.key).to.equal('a'); + chai.expect(map.zeroNode.nextNode.nextNode.nextNode.value.letter).to.equal('a'); + chai.expect(map.size).to.equal(3); + }); + + it('addRequestResponsePair limit check', async () => { + lookupCache.addRequestResponsePair('a', { letter: 'a' }); + lookupCache.addRequestResponsePair('b', { letter: 'b' }); + lookupCache.addRequestResponsePair('c', { letter: 'c' }); + lookupCache.addRequestResponsePair('d', { letter: 'd' }); + lookupCache.addRequestResponsePair('e', { letter: 'e' }); + lookupCache.addRequestResponsePair('f', { letter: 'f' }); + lookupCache.addRequestResponsePair('g', { letter: 'g' }); + lookupCache.addRequestResponsePair('h', { letter: 'h' }); + lookupCache.addRequestResponsePair('i', { letter: 'i' }); + lookupCache.addRequestResponsePair('j', { letter: 'j' }); + lookupCache.addRequestResponsePair('k', { letter: 'k' }); + lookupCache.addRequestResponsePair('l', { letter: 'l' }); + + const map = lookupCache.getMap(); + chai.expect(map.size).to.equal(10); + chai.expect(map.zeroNode.nextNode.key).to.equal('l'); + chai.expect(map.zeroNode.nextNode.value.letter).to.equal('l'); + + chai.expect(lookupCache.hasKey('a')).to.equal(false); + chai.expect(lookupCache.hasKey('b')).to.equal(false); + chai.expect(lookupCache.hasKey('c')).to.equal(true); + chai.expect(lookupCache.hasKey('d')).to.equal(true); + }); + + it('getResponse', async () => { + lookupCache.addRequestResponsePair('a', { letter: 'a' }); + lookupCache.addRequestResponsePair('b', { letter: 'b' }); + lookupCache.addRequestResponsePair('c', { letter: 'c' }); + + const result = lookupCache.getResponse('b'); + + chai.expect(result).to.deep.equal({ letter: 'b' }); + + const map = lookupCache.getMap(); + chai.expect(map.zeroNode.nextNode.key).to.equal('b'); + chai.expect(map.zeroNode.nextNode.value.letter).to.equal('b'); + chai.expect(map.zeroNode.nextNode.nextNode.key).to.equal('c'); + chai.expect(map.zeroNode.nextNode.nextNode.value.letter).to.equal('c'); + chai.expect(map.zeroNode.nextNode.nextNode.nextNode.key).to.equal('a'); + chai.expect(map.zeroNode.nextNode.nextNode.nextNode.value.letter).to.equal('a'); + chai.expect(map.size).to.equal(3); + }); + + it('clear', async () => { + lookupCache.addRequestResponsePair('a', { letter: 'a' }); + lookupCache.addRequestResponsePair('b', { letter: 'b' }); + lookupCache.addRequestResponsePair('c', { letter: 'c' }); + + lookupCache.clear(); + + chai.expect(lookupCache.getResponse('a')).to.equal(undefined); + chai.expect(lookupCache.getResponse('b')).to.equal(undefined); + chai.expect(lookupCache.getResponse('c')).to.equal(undefined); + chai.expect(lookupCache.getMap().size).to.equal(0); + }); + + it('generateKeyFromDataArray', async () => { + const result = lookupCache.generateKeyFromDataArray('a', 1, -5, true, ['1', 'a', 5]); + chai.expect(result).to.equal('a1-5true1,a,5'); + }); }); - beforeEach(() => { - lookupCache.enableCache(); - }); - - afterEach(() => { - lookupCache.clear(); - }); - - it('getMap', async () => { - const map = lookupCache.getMap(); - chai.expect(map).to.equal(lookupCache.requestCache); - }); - - it('hasKey', async () => { - const map = lookupCache.getMap(); - - map.set('a', { letter: 'a' }); - map.set('b', { letter: 'b' }); - map.set('c', { letter: 'c' }); - - chai.expect(lookupCache.hasKey('a')).to.equal(true); - chai.expect(lookupCache.hasKey('b')).to.equal(true); - chai.expect(lookupCache.hasKey('c')).to.equal(true); - chai.expect(lookupCache.hasKey('d')).to.equal(false); - }); - - it('addRequestResponsePair', async () => { - lookupCache.addRequestResponsePair('a', { letter: 'a' }); - lookupCache.addRequestResponsePair('b', { letter: 'b' }); - lookupCache.addRequestResponsePair('c', { letter: 'c' }); - - const map = lookupCache.getMap(); - - chai.expect(map.zeroNode.nextNode.key).to.equal('c'); - chai.expect(map.zeroNode.nextNode.value.letter).to.equal('c'); - chai.expect(map.zeroNode.nextNode.nextNode.key).to.equal('b'); - chai.expect(map.zeroNode.nextNode.nextNode.value.letter).to.equal('b'); - chai.expect(map.zeroNode.nextNode.nextNode.nextNode.key).to.equal('a'); - chai.expect(map.zeroNode.nextNode.nextNode.nextNode.value.letter).to.equal('a'); - chai.expect(map.size).to.equal(3); - }); - - it('addRequestResponsePair limit check', async () => { - lookupCache.addRequestResponsePair('a', { letter: 'a' }); - lookupCache.addRequestResponsePair('b', { letter: 'b' }); - lookupCache.addRequestResponsePair('c', { letter: 'c' }); - lookupCache.addRequestResponsePair('d', { letter: 'd' }); - lookupCache.addRequestResponsePair('e', { letter: 'e' }); - lookupCache.addRequestResponsePair('f', { letter: 'f' }); - lookupCache.addRequestResponsePair('g', { letter: 'g' }); - lookupCache.addRequestResponsePair('h', { letter: 'h' }); - lookupCache.addRequestResponsePair('i', { letter: 'i' }); - lookupCache.addRequestResponsePair('j', { letter: 'j' }); - lookupCache.addRequestResponsePair('k', { letter: 'k' }); - lookupCache.addRequestResponsePair('l', { letter: 'l' }); - - const map = lookupCache.getMap(); - chai.expect(map.size).to.equal(10); - chai.expect(map.zeroNode.nextNode.key).to.equal('l'); - chai.expect(map.zeroNode.nextNode.value.letter).to.equal('l'); - - chai.expect(lookupCache.hasKey('a')).to.equal(false); - chai.expect(lookupCache.hasKey('b')).to.equal(false); - chai.expect(lookupCache.hasKey('c')).to.equal(true); - chai.expect(lookupCache.hasKey('d')).to.equal(true); - }); + describe('Linked Limited Map class unit tests', () => { + before(() => { + lookupCache.clear(); + }); - it('getResponse', async () => { - lookupCache.addRequestResponsePair('a', { letter: 'a' }); - lookupCache.addRequestResponsePair('b', { letter: 'b' }); - lookupCache.addRequestResponsePair('c', { letter: 'c' }); - - const result = lookupCache.getResponse('b'); - - chai.expect(result).to.deep.equal({ letter: 'b' }); - - const map = lookupCache.getMap(); - chai.expect(map.zeroNode.nextNode.key).to.equal('b'); - chai.expect(map.zeroNode.nextNode.value.letter).to.equal('b'); - chai.expect(map.zeroNode.nextNode.nextNode.key).to.equal('c'); - chai.expect(map.zeroNode.nextNode.nextNode.value.letter).to.equal('c'); - chai.expect(map.zeroNode.nextNode.nextNode.nextNode.key).to.equal('a'); - chai.expect(map.zeroNode.nextNode.nextNode.nextNode.value.letter).to.equal('a'); - chai.expect(map.size).to.equal(3); - }); - - it('clear', async () => { - lookupCache.addRequestResponsePair('a', { letter: 'a' }); - lookupCache.addRequestResponsePair('b', { letter: 'b' }); - lookupCache.addRequestResponsePair('c', { letter: 'c' }); - - lookupCache.clear(); - - chai.expect(lookupCache.getResponse('a')).to.equal(undefined); - chai.expect(lookupCache.getResponse('b')).to.equal(undefined); - chai.expect(lookupCache.getResponse('c')).to.equal(undefined); - chai.expect(lookupCache.getMap().size).to.equal(0); - }); - - it('generateKeyFromDataArray', async () => { - const result = lookupCache.generateKeyFromDataArray('a', 1, -5, true, ['1', 'a', 5]); - chai.expect(result).to.equal('a1-5true1,a,5'); - }); -}); - -describe('Linked Limited Map class unit tests', () => { - before(() => { - lookupCache.clear(); - }); - - afterEach(() => { - lookupCache.clear(); - }); + afterEach(() => { + lookupCache.clear(); + }); - it('delete', async () => { - const map = lookupCache.getMap(); + it('delete', async () => { + const map = lookupCache.getMap(); - map.set('a', { letter: 'a' }); - map.set('b', { letter: 'b' }); - map.set('c', { letter: 'c' }); + map.set('a', { letter: 'a' }); + map.set('b', { letter: 'b' }); + map.set('c', { letter: 'c' }); - map.delete('b'); + map.delete('b'); - chai.expect(map.zeroNode.nextNode.key).to.equal('c'); - chai.expect(map.zeroNode.nextNode.value.letter).to.equal('c'); - chai.expect(map.zeroNode.nextNode.nextNode.key).to.equal('a'); - chai.expect(map.zeroNode.nextNode.nextNode.value.letter).to.equal('a'); - chai.expect(map.size).to.equal(2); + chai.expect(map.zeroNode.nextNode.key).to.equal('c'); + chai.expect(map.zeroNode.nextNode.value.letter).to.equal('c'); + chai.expect(map.zeroNode.nextNode.nextNode.key).to.equal('a'); + chai.expect(map.zeroNode.nextNode.nextNode.value.letter).to.equal('a'); + chai.expect(map.size).to.equal(2); - map.delete('c'); + map.delete('c'); - chai.expect(map.zeroNode.nextNode.key).to.equal('a'); - chai.expect(map.zeroNode.nextNode.value.letter).to.equal('a'); + chai.expect(map.zeroNode.nextNode.key).to.equal('a'); + chai.expect(map.zeroNode.nextNode.value.letter).to.equal('a'); - chai.expect(map.size).to.equal(1); + chai.expect(map.size).to.equal(1); + }); }); }); diff --git a/spec/helpers/metadata.spec.js b/spec/helpers/metadata.spec.js deleted file mode 100644 index 9df5321..0000000 --- a/spec/helpers/metadata.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -const chai = require('chai'); - -const { expect } = chai; -const metadata = require('../../lib/helpers/metadata'); -const description = require('../testData/objectDescriptionForMetadata'); -const expectedInputMetadata = require('../testData/expectedMetadataIn'); -const expectedOutputMetadata = require('../testData/expectedMetadataOut'); - -describe('Metadata to select fields conversion', () => { - describe('Test pickSelectFields', () => { - it('should convert metadata out properties to a comma separated string value', () => { - const input = { - description: 'Contact', - type: 'object', - properties: { - LastName: {}, - FirstName: {}, - Jigsaw: {}, - Level__c: {}, - Languages__c: {}, - }, - }; - - const result = metadata.pickSelectFields(input); - expect(result).to.equal('LastName,FirstName,Jigsaw,Level__c,Languages__c'); - }); - - it('should throw an exception if metadata.out doesn\'t exist', () => { - expect(() => { - metadata.pickSelectFields({}); - }).to.throw('No out metadata found to create select fields from'); - }); - }); - - describe('Test buildSchemaFromDescription', () => { - it('should buildSchemaFromDescription for input metadata', () => { - const result = metadata.buildSchemaFromDescription(description, 'in'); - expect(result).to.deep.equal(expectedInputMetadata); - }); - - it('should buildSchemaFromDescription for output metadata', () => { - const result = metadata.buildSchemaFromDescription(description, 'out'); - expect(result).to.deep.equal(expectedOutputMetadata); - }); - }); -}); diff --git a/spec/helpers/oauth-utils.spec.js b/spec/helpers/oauth-utils.spec.js deleted file mode 100644 index 2510e2a..0000000 --- a/spec/helpers/oauth-utils.spec.js +++ /dev/null @@ -1,62 +0,0 @@ -const sinon = require('sinon'); -const { expect } = require('chai'); -const logger = require('@elastic.io/component-logger')(); -const helper = require('../../lib/helpers/oauth-utils'); -const httpUtils = require('../../lib/helpers/http-utils'); - -describe('oauth-utils Unit Test', () => { - const configuration = { - apiVersion: '39.0', - oauth: { - issued_at: '1541510572760', - token_type: 'Bearer', - id: 'https://login.salesforce.com/id/11/11', - instance_url: 'https://example.com', - id_token: 'ddd', - scope: 'refresh_token full', - signature: '=', - refresh_token: 'refresh_token', - access_token: 'access_token', - }, - object: 'Contact', - }; - const serviceURI = 'https://serviceuri.com/testing'; - const clientIdKey = 'clientIdKey'; - const clientSecretKey = 'clientSecretKey'; - - afterEach(() => { - sinon.restore(); - }); - - it('should return a new configuration object', async () => { - const refreshResponse = { - access_token: 'newAccessToken', - refresh_token: 'newRefreshToken', - }; - sinon.stub(httpUtils, 'getJSON').callsFake((log, params, next) => { - next(null, refreshResponse); - }); - - helper.refreshToken(logger, serviceURI, clientIdKey, clientSecretKey, configuration, - (error, newConf) => { - expect(newConf.oauth.access_token).to.equal('newAccessToken'); - expect(newConf.oauth.refresh_token).to.equal('newRefreshToken'); - }); - }); - - it('should throw an error', () => { - const error = { - message: 'some error thrown', - statusCode: 404, - }; - sinon.stub(httpUtils, 'getJSON').callsFake((log, params, next) => { - next(error); - }); - - // eslint-disable-next-line max-len - helper.refreshToken(logger, serviceURI, clientIdKey, clientSecretKey, configuration, (err) => { - expect(err.message).to.equal('some error thrown'); - expect(err.statusCode).to.equal(404); - }); - }); -}); diff --git a/spec/helpers/objectFetcher.spec.js b/spec/helpers/objectFetcher.spec.js deleted file mode 100644 index 5358b7b..0000000 --- a/spec/helpers/objectFetcher.spec.js +++ /dev/null @@ -1,73 +0,0 @@ -const nock = require('nock'); -const chai = require('chai'); -const logger = require('@elastic.io/component-logger')(); - -const { expect } = chai; -const objectFetcher = require('../../lib/helpers/objectFetcher'); - -const params = {}; - -describe('Fetching objects', () => { - beforeEach(() => { - params.cfg = { - oauth: { - access_token: '00DE0000000dwKc!ARcAQEgJMHustyszwbrh06CGCuYPn2aI..bAV4T8aA8aDXRpAWeveWq7jhUlGl7d2e7T8itqCX1F0f_LeuMDiGzZrdOIIVnE', - refresh_token: '5Aep861rEpScxnNE66jGO6gqeJ82V9qXOs5YIxlkVZgWYMSJfjLeqYUwKNfA2R7cU04EyjVKE9_A.vqQY9kjgUg', - instance_url: 'https://na9.salesforce.com', - }, - apiVersion: 'v25.0', - object: 'Contact', - }; - params.snapshot = '1978-04-06T11:00:00.000Z'; - params.selectFields = 'LastName,FirstName,Salutation,OtherStreet,OtherCity,OtherState,OtherPostalCode,OtherCountry,MailingStreet,MailingCity,MailingState,MailingPostalCode,MailingCountry,Phone,Fax,MobilePhone,HomePhone,OtherPhone,AssistantPhone,Email,Title,Department,AssistantName,LeadSource,Birthdate,Description,EmailBouncedReason,EmailBouncedDate,Jigsaw,Level__c,Languages__c'; - }); - describe('should succeed', () => { - it('should fetch empty array of specified type with provided query', async () => { - const expectedResult = []; - - nock('https://na9.salesforce.com') - .get('/services/data/v25.0/query?q=select%20LastName%2CFirstName%2CSalutation%2COtherStreet%2COtherCity%2COtherState%2COtherPostalCode%2COtherCountry%2CMailingStreet%2CMailingCity%2CMailingState%2CMailingPostalCode%2CMailingCountry%2CPhone%2CFax%2CMobilePhone%2CHomePhone%2COtherPhone%2CAssistantPhone%2CEmail%2CTitle%2CDepartment%2CAssistantName%2CLeadSource%2CBirthdate%2CDescription%2CEmailBouncedReason%2CEmailBouncedDate%2CJigsaw%2CLevel__c%2CLanguages__c%20from%20Contact%20where%20SystemModstamp%20%3E%201978-04-06T11%3A00%3A00.000Z') - .reply(200, []); - - const result = await objectFetcher(logger, params); - expect(result.objects).to.deep.equal(expectedResult); - }); - - it('should fetch objects of specified type with provided query', async () => { - const expectedResult = [{ one: 1 }, { two: 2 }]; - - nock('https://na9.salesforce.com') - .get('/services/data/v25.0/query?q=select%20LastName%2CFirstName%2CSalutation%2COtherStreet%2COtherCity%2COtherState%2COtherPostalCode%2COtherCountry%2CMailingStreet%2CMailingCity%2CMailingState%2CMailingPostalCode%2CMailingCountry%2CPhone%2CFax%2CMobilePhone%2CHomePhone%2COtherPhone%2CAssistantPhone%2CEmail%2CTitle%2CDepartment%2CAssistantName%2CLeadSource%2CBirthdate%2CDescription%2CEmailBouncedReason%2CEmailBouncedDate%2CJigsaw%2CLevel__c%2CLanguages__c%20from%20Contact%20where%20SystemModstamp%20%3E%201978-04-06T11%3A00%3A00.000Z') - .reply(200, [{ one: 1 }, { two: 2 }]); - - const result = await objectFetcher(logger, params); - expect(result.objects).to.deep.equal(expectedResult); - }); - }); - - describe('should throw an error', () => { - it('should throw an error if no snapshot provided', () => { - params.snapshot = undefined; - - expect(() => { objectFetcher(logger, params); }).to.throw('Can\'t fetch objects without a predefined snapshot'); - }); - - it('should throw an error if no cfg provided', () => { - params.cfg = undefined; - - expect(() => { objectFetcher(logger, params); }).to.throw('Can\'t fetch objects without a configuration parameter'); - }); - - it('should throw an error if no apiVersion provided', () => { - params.cfg.apiVersion = undefined; - - expect(() => { objectFetcher(logger, params); }).to.throw('Can\'t fetch objects without an apiVersion'); - }); - - it('should throw an error if no object provided', () => { - params.cfg.object = undefined; - - expect(() => { objectFetcher(logger, params); }).to.throw('Can\'t fetch objects without an object type'); - }); - }); -}); diff --git a/spec/helpers/objectFetcherQuery.spec.js b/spec/helpers/objectFetcherQuery.spec.js deleted file mode 100644 index fe1110c..0000000 --- a/spec/helpers/objectFetcherQuery.spec.js +++ /dev/null @@ -1,62 +0,0 @@ -const nock = require('nock'); -const chai = require('chai'); -const logger = require('@elastic.io/component-logger')(); - -const { expect } = chai; -const fetchObjects = require('../../lib/helpers/objectFetcherQuery'); - -let params = {}; -const token = 'token'; - -describe('Fetching objects', () => { - beforeEach(() => { - params = { - cfg: { - oauth: { - access_token: token, - instance_url: 'https://eu11.salesforce.com', - }, - apiVersion: 'v25.0', - }, - query: 'SELECT id, LastName, FirstName FROM Contact', - }; - }); - - describe('should succeed', () => { - it('should send given query and return result', async () => { - const expectedResult = []; - nock('https://eu11.salesforce.com') - .get('/services/data/v25.0/query?q=SELECT%20id%2C%20LastName%2C%20FirstName%20FROM%20Contact') - .matchHeader('Authorization', `Bearer ${token}`) - .reply(200, []); - - const result = await fetchObjects(logger, params); - expect(result.objects).to.deep.equal(expectedResult); - }); - }); - describe('should throw an error', () => { - it('should throw an error if no cfg provided', () => { - delete params.cfg; - - expect(() => { - fetchObjects(logger, params); - }).to.throw('Can\'t fetch objects without a configuration parameter'); - }); - - it('should throw an error if no apiVersion provided', () => { - params.cfg.apiVersion = undefined; - - expect(() => { - fetchObjects(logger, params); - }).to.throw('Can\'t fetch objects without an apiVersion'); - }); - - it('should throw an error if no object provided', () => { - delete params.query; - - expect(() => { - fetchObjects(logger, params); - }).to.throw('Can\'t fetch objects without a query'); - }); - }); -}); diff --git a/spec/helpers/utils.spec.js b/spec/helpers/utils.spec.js new file mode 100644 index 0000000..8731b9d --- /dev/null +++ b/spec/helpers/utils.spec.js @@ -0,0 +1,165 @@ +const { expect } = require('chai'); +const { processMeta, getLinkedObjectTypes, getLookupFieldsModelWithTypeOfSearch } = require('../../lib/helpers/utils'); +const contactDescription = require('../testData/objectDescription.json'); +const metaModelDocumentReply = require('../testData/sfDocumentMetadata.json'); + +describe('utils helper', () => { + describe('processMeta helper', () => { + const meta = contactDescription; + it('should succeed create metadata for metaType create', async () => { + const metaType = 'create'; + const result = await processMeta(meta, metaType); + expect(result.in.properties.LastName).to.eql({ + type: 'string', + required: true, + title: 'Last Name', + default: null, + }); + expect(result.out.properties.id).to.eql({ + type: 'string', + required: true, + }); + }); + + it('should succeed create metadata for metaType upsert', async () => { + const metaType = 'upsert'; + const result = await processMeta(meta, metaType); + expect(result.in.properties.LastName).to.eql({ + type: 'string', + required: false, + title: 'Last Name', + default: null, + }); + }); + + it('should succeed create metadata for metaType lookup', async () => { + const metaType = 'lookup'; + const lookupField = 'Id'; + + const result = await processMeta(meta, metaType, lookupField); + expect(result.in.properties).to.eql({ + Id: { + default: null, + required: false, + title: 'Contact ID', + type: 'string', + }, + }); + }); + }); + + describe('getLinkedObjectTypes helper', () => { + it('should succeed getLinkedObjectTypes Document', async () => { + const expectedResult = { + Folder: 'Folder, User (Folder)', + Author: 'User (Author)', + CreatedBy: 'User (CreatedBy)', + LastModifiedBy: 'User (LastModifiedBy)', + }; + const result = await getLinkedObjectTypes(metaModelDocumentReply); + expect(result).to.eql(expectedResult); + }); + + it('should succeed getLinkedObjectTypes Contact', async () => { + const expectedResult = { + MasterRecord: 'Contact (MasterRecord)', + Account: 'Account (Account)', + ReportsTo: 'Contact (ReportsTo)', + Owner: 'User (Owner)', + CreatedBy: 'User (CreatedBy)', + LastModifiedBy: 'User (LastModifiedBy)', + '!AccountContactRoles': 'AccountContactRole (AccountContactRoles)', + '!ActivityHistories': 'ActivityHistory (ActivityHistories)', + '!Assets': 'Asset (Assets)', + '!Attachments': 'Attachment (Attachments)', + '!CampaignMembers': 'CampaignMember (CampaignMembers)', + '!Cases': 'Case (Cases)', + '!CaseContactRoles': 'CaseContactRole (CaseContactRoles)', + '!Feeds': 'ContactFeed (Feeds)', + '!Histories': 'ContactHistory (Histories)', + '!Shares': 'ContactShare (Shares)', + '!ContractsSigned': 'Contract (ContractsSigned)', + '!ContractContactRoles': 'ContractContactRole (ContractContactRoles)', + '!EmailStatuses': 'EmailStatus (EmailStatuses)', + '!FeedSubscriptionsForEntity': 'EntitySubscription (FeedSubscriptionsForEntity)', + '!Events': 'Event (Events)', + '!Notes': 'Note (Notes)', + '!NotesAndAttachments': 'NoteAndAttachment (NotesAndAttachments)', + '!OpenActivities': 'OpenActivity (OpenActivities)', + '!OpportunityContactRoles': 'OpportunityContactRole (OpportunityContactRoles)', + '!ProcessInstances': 'ProcessInstance (ProcessInstances)', + '!ProcessSteps': 'ProcessInstanceHistory (ProcessSteps)', + '!Tasks': 'Task (Tasks)', + }; + const result = await getLinkedObjectTypes(contactDescription); + expect(result).to.eql(expectedResult); + }); + }); + + describe('getLookupFieldsModelWithTypeOfSearch helper', () => { + const typesOfSearch = { + uniqueFields: 'uniqueFields', + allFields: 'allFields', + }; + + it('should succeed getLookupFieldsModelWithTypeOfSearch Contact uniqueFields', async () => { + const result = await getLookupFieldsModelWithTypeOfSearch(contactDescription, typesOfSearch.uniqueFields); + expect(result).to.eql({ + Id: 'Contact ID (Id)', + }); + }); + + it('should succeed getLookupFieldsModelWithTypeOfSearch Contact allFields', async () => { + const result = await getLookupFieldsModelWithTypeOfSearch(contactDescription, typesOfSearch.allFields); + expect(result).to.eql({ + Id: 'Contact ID (Id)', + IsDeleted: 'Deleted (IsDeleted)', + MasterRecordId: 'Master Record ID (MasterRecordId)', + AccountId: 'Account ID (AccountId)', + LastName: 'Last Name (LastName)', + FirstName: 'First Name (FirstName)', + Salutation: 'Salutation (Salutation)', + Name: 'Full Name (Name)', + OtherStreet: 'Other Street (OtherStreet)', + OtherCity: 'Other City (OtherCity)', + OtherState: 'Other State/Province (OtherState)', + OtherPostalCode: 'Other Zip/Postal Code (OtherPostalCode)', + OtherCountry: 'Other Country (OtherCountry)', + MailingStreet: 'Mailing Street (MailingStreet)', + MailingCity: 'Mailing City (MailingCity)', + MailingState: 'Mailing State/Province (MailingState)', + MailingPostalCode: 'Mailing Zip/Postal Code (MailingPostalCode)', + MailingCountry: 'Mailing Country (MailingCountry)', + Phone: 'Business Phone (Phone)', + Fax: 'Business Fax (Fax)', + MobilePhone: 'Mobile Phone (MobilePhone)', + HomePhone: 'Home Phone (HomePhone)', + OtherPhone: 'Other Phone (OtherPhone)', + AssistantPhone: 'Asst. Phone (AssistantPhone)', + ReportsToId: 'Reports To ID (ReportsToId)', + Email: 'Email (Email)', + Title: 'Title (Title)', + Department: 'Department (Department)', + AssistantName: "Assistant's Name (AssistantName)", + LeadSource: 'Lead Source (LeadSource)', + Birthdate: 'Birthdate (Birthdate)', + Description: 'Contact Description (Description)', + OwnerId: 'Owner ID (OwnerId)', + CreatedDate: 'Created Date (CreatedDate)', + CreatedById: 'Created By ID (CreatedById)', + LastModifiedDate: 'Last Modified Date (LastModifiedDate)', + LastModifiedById: 'Last Modified By ID (LastModifiedById)', + SystemModstamp: 'System Modstamp (SystemModstamp)', + LastActivityDate: 'Last Activity (LastActivityDate)', + LastCURequestDate: 'Last Stay-in-Touch Request Date (LastCURequestDate)', + LastCUUpdateDate: 'Last Stay-in-Touch Save Date (LastCUUpdateDate)', + EmailBouncedReason: 'Email Bounced Reason (EmailBouncedReason)', + EmailBouncedDate: 'Email Bounced Date (EmailBouncedDate)', + Jigsaw: 'Data.com Key (Jigsaw)', + JigsawContactId: 'Jigsaw Contact ID (JigsawContactId)', + Level__c: 'Level (Level__c)', + Languages__c: 'Languages (Languages__c)', + }); + }); + }); +}); diff --git a/spec/helpers/wrapper.spec.js b/spec/helpers/wrapper.spec.js new file mode 100644 index 0000000..472f8e0 --- /dev/null +++ b/spec/helpers/wrapper.spec.js @@ -0,0 +1,74 @@ +const { expect } = require('chai'); +const nock = require('nock'); +const logger = require('@elastic.io/component-logger')(); +const common = require('../../lib/common.js'); +const testCommon = require('../common.js'); +const { callJSForceMethod } = require('../../lib/helpers/wrapper'); + +describe('wrapper helper', () => { + afterEach(() => { + nock.cleanAll(); + }); + it('should succeed call describe method', async () => { + const cfg = { + secretId: testCommon.secretId, + sobject: 'Contact', + }; + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .reply(200, testCommon.secret); + nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Contact/describe`) + .reply(200, { name: 'Contact' }); + const result = await callJSForceMethod.call({ logger }, cfg, 'describe'); + expect(result.name).to.eql('Contact'); + }); + + it('should succeed call describe method, credentials from config', async () => { + const cfg = { + sobject: 'Contact', + oauth: { + access_token: 'access_token', + undefined_params: { + instance_url: testCommon.instanceUrl, + }, + }, + }; + nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Contact/describe`) + .reply(200, { name: 'Contact' }); + const result = await callJSForceMethod.call({ logger }, cfg, 'describe'); + expect(result.name).to.eql('Contact'); + }); + + it('should refresh token and succeed call describe method', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .reply(200, { + data: { + attributes: { + credentials: { + access_token: 'oldAccessToken', + undefined_params: { + instance_url: testCommon.instanceUrl, + }, + }, + }, + }, + }) + .post(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}/refresh`) + .reply(200, testCommon.secret); + const cfg = { + secretId: testCommon.secretId, + sobject: 'Contact', + }; + nock(testCommon.instanceUrl, { reqheaders: { authorization: 'Bearer oldAccessToken' } }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Contact/describe`) + .replyWithError({ name: 'INVALID_SESSION_ID' }); + nock(testCommon.instanceUrl, { reqheaders: { authorization: 'Bearer accessToken' } }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Contact/describe`) + .reply(200, { name: 'Contact' }); + const result = await callJSForceMethod.call({ logger }, cfg, 'describe'); + expect(result.name).to.eql('Contact'); + }); +}); diff --git a/spec/actions/bulk_cud.json b/spec/testData/bulk_cud.json similarity index 98% rename from spec/actions/bulk_cud.json rename to spec/testData/bulk_cud.json index bc11f16..e25740e 100644 --- a/spec/actions/bulk_cud.json +++ b/spec/testData/bulk_cud.json @@ -1,140 +1,140 @@ -{ - "bulkInsertCase": { - "message": { - "body": { - }, - "attachments": { - "cases_insert.csv": { - "url": "http://file.storage.server/cases_insert.csv", - "content-type": "application/octet-stream" - } - }, - "id": "f4e0d892-2bd0-490f-a8c2-281ac0383ad8" - }, - "configuration": { - "sobject": "Case", - "operation": "insert" - }, - "responses": { - "http://file.storage.server": { - "/cases_insert.csv": { - "method": "GET", - "response": "Type,Reason,SuppliedName\nQuestion,R1,SN1\nQuestion,R2,SN2\nQuestion,R3,SN3" - } - }, - "https://test.salesforce.com": { - "/services/async/46.0/job": { - "method": "POST", - "response": " 7502o00000JqBYLAA3 insert Case 0052o000008w0iqAAA 2019-09-18T09:55:24.000Z 2019-09-18T09:55:24.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JqBYLAA3/batch": { - "method": "POST", - "response": " 7512o00000QcYGDAA3 7502o00000JqBYLAA3 Queued 2019-09-18T09:55:24.000Z 2019-09-18T09:55:24.000Z 0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JqBYLAA3/batch/7512o00000QcYGDAA3": { - "method": "GET", - "response": " 7512o00000QcYGDAA3 7502o00000JqBYLAA3 Completed 2019-09-18T09:55:24.000Z 2019-09-18T09:55:25.000Z 3 0 371 79 0 " - }, - "/services/async/46.0/job/7502o00000JqBYLAA3/batch/7512o00000QcYGDAA3/result": { - "method": "GET", - "header": { - "content-type": "text/csv" - }, - "response": "\"Id\",\"Success\",\"Created\",\"Error\"\n\"5002o00002E8FIIAA3\",\"true\",\"true\",\"\"\n\"5002o00002E8FIJAA3\",\"true\",\"true\",\"\"\n\"5002o00002E8FIKAA3\",\"true\",\"true\",\"\"" - } - } - } - }, - "bulkUpdateCase": { - "message": { - "attachments": { - "cases_update.csv": { - "url": "http://file.storage.server/cases_update.csv", - "content-type": "application/octet-stream" - } - }, - "body": { - }, - "id": "f4e0d892-2bd0-490f-a8c2-281ac0383ad8" - }, - "configuration": { - "sobject": "Case", - "operation": "update" - }, - "responses": { - "http://file.storage.server": { - "/cases_update.csv": { - "method": "GET", - "response": "id,SuppliedName\n5002o00002E89BbAAJ,SN2222222" - } - }, - "https://test.salesforce.com": { - "/services/async/46.0/job": { - "method": "POST", - "response": " 7502o00000JqFMTAA3 update Case 0052o000008w0iqAAA 2019-09-18T15:07:31.000Z 2019-09-18T15:07:31.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JqFMTAA3/batch": { - "method": "POST", - "response": " 7512o00000QcdHVAAZ 7502o00000JqFMTAA3 Queued 2019-09-18T15:07:31.000Z 2019-09-18T15:07:31.000Z 0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JqFMTAA3/batch/7512o00000QcdHVAAZ": { - "method": "GET", - "response": " 7512o00000QcdHVAAZ 7502o00000JqFMTAA3 Completed 2019-09-18T15:07:31.000Z 2019-09-18T15:07:31.000Z 1 1 115 11 0 " - }, - "/services/async/46.0/job/7502o00000JqFMTAA3/batch/7512o00000QcdHVAAZ/result": { - "method": "GET", - "header": { - "Content-Type": "text/csv" - }, - "response": "\"Id\",\"Success\",\"Created\",\"Error\"\n\"\",\"false\",\"false\",\"ENTITY_IS_DELETED:entity is deleted:--\"" - } - } - } - }, - "bulkDeleteCase": { - "message": { - "body": { - }, - "attachments": { - "cases_delete.csv": { - "url": "http://file.storage.server/cases_delete.csv", - "content-type": "application/octet-stream" - } - }, - "id": "db50d0c4-0370-4d6f-b5e0-8242f1ffeb48" - }, - "configuration": { - "sobject": "Case", - "operation": "delete" - }, - "responses": { - "http://file.storage.server": { - "/cases_delete.csv": { - "method": "GET", - "response": "id\n5002o00002BT0IUAA1" - } - }, - "https://test.salesforce.com": { - "/services/async/46.0/job": { - "method": "POST", - "response": " 7502o00000JrBezAAF delete Case 0052o000008w0iqAAA 2019-09-23T08:16:04.000Z 2019-09-23T08:16:04.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JrBezAAF/batch": { - "method": "POST", - "response": " 7512o00000Qdvv6AAB 7502o00000JrBezAAF Queued 2019-09-23T08:16:04.000Z 2019-09-23T08:16:04.000Z 0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JrBezAAF/batch/7512o00000Qdvv6AAB": { - "method": "GET", - "response": " 7512o00000Qdvv6AAB 7502o00000JrBezAAF Completed 2019-09-23T08:16:04.000Z 2019-09-23T08:16:11.000Z 1 0 557 453 0 " - }, - "/services/async/46.0/job/7502o00000JrBezAAF/batch/7512o00000Qdvv6AAB/result": { - "method": "GET", - "header": { - "Content-Type": "text/csv" - }, - "response": "\"Id\",\"Success\",\"Created\",\"Error\"\n\"5002o00002BT0IUAA1\",\"true\",\"false\",\"\"" - } - } - } - } +{ + "bulkInsertCase": { + "message": { + "body": { + }, + "attachments": { + "cases_insert.csv": { + "url": "http://file.storage.server/cases_insert.csv", + "content-type": "application/octet-stream" + } + }, + "id": "f4e0d892-2bd0-490f-a8c2-281ac0383ad8" + }, + "configuration": { + "sobject": "Case", + "operation": "insert" + }, + "responses": { + "http://file.storage.server": { + "/cases_insert.csv": { + "method": "GET", + "response": "Type,Reason,SuppliedName\nQuestion,R1,SN1\nQuestion,R2,SN2\nQuestion,R3,SN3" + } + }, + "https://test.salesforce.com": { + "/services/async/46.0/job": { + "method": "POST", + "response": " 7502o00000JqBYLAA3 insert Case 0052o000008w0iqAAA 2019-09-18T09:55:24.000Z 2019-09-18T09:55:24.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JqBYLAA3/batch": { + "method": "POST", + "response": " 7512o00000QcYGDAA3 7502o00000JqBYLAA3 Queued 2019-09-18T09:55:24.000Z 2019-09-18T09:55:24.000Z 0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JqBYLAA3/batch/7512o00000QcYGDAA3": { + "method": "GET", + "response": " 7512o00000QcYGDAA3 7502o00000JqBYLAA3 Completed 2019-09-18T09:55:24.000Z 2019-09-18T09:55:25.000Z 3 0 371 79 0 " + }, + "/services/async/46.0/job/7502o00000JqBYLAA3/batch/7512o00000QcYGDAA3/result": { + "method": "GET", + "header": { + "content-type": "text/csv" + }, + "response": "\"Id\",\"Success\",\"Created\",\"Error\"\n\"5002o00002E8FIIAA3\",\"true\",\"true\",\"\"\n\"5002o00002E8FIJAA3\",\"true\",\"true\",\"\"\n\"5002o00002E8FIKAA3\",\"true\",\"true\",\"\"" + } + } + } + }, + "bulkUpdateCase": { + "message": { + "attachments": { + "cases_update.csv": { + "url": "http://file.storage.server/cases_update.csv", + "content-type": "application/octet-stream" + } + }, + "body": { + }, + "id": "f4e0d892-2bd0-490f-a8c2-281ac0383ad8" + }, + "configuration": { + "sobject": "Case", + "operation": "update" + }, + "responses": { + "http://file.storage.server": { + "/cases_update.csv": { + "method": "GET", + "response": "id,SuppliedName\n5002o00002E89BbAAJ,SN2222222" + } + }, + "https://test.salesforce.com": { + "/services/async/46.0/job": { + "method": "POST", + "response": " 7502o00000JqFMTAA3 update Case 0052o000008w0iqAAA 2019-09-18T15:07:31.000Z 2019-09-18T15:07:31.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JqFMTAA3/batch": { + "method": "POST", + "response": " 7512o00000QcdHVAAZ 7502o00000JqFMTAA3 Queued 2019-09-18T15:07:31.000Z 2019-09-18T15:07:31.000Z 0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JqFMTAA3/batch/7512o00000QcdHVAAZ": { + "method": "GET", + "response": " 7512o00000QcdHVAAZ 7502o00000JqFMTAA3 Completed 2019-09-18T15:07:31.000Z 2019-09-18T15:07:31.000Z 1 1 115 11 0 " + }, + "/services/async/46.0/job/7502o00000JqFMTAA3/batch/7512o00000QcdHVAAZ/result": { + "method": "GET", + "header": { + "Content-Type": "text/csv" + }, + "response": "\"Id\",\"Success\",\"Created\",\"Error\"\n\"\",\"false\",\"false\",\"ENTITY_IS_DELETED:entity is deleted:--\"" + } + } + } + }, + "bulkDeleteCase": { + "message": { + "body": { + }, + "attachments": { + "cases_delete.csv": { + "url": "http://file.storage.server/cases_delete.csv", + "content-type": "application/octet-stream" + } + }, + "id": "db50d0c4-0370-4d6f-b5e0-8242f1ffeb48" + }, + "configuration": { + "sobject": "Case", + "operation": "delete" + }, + "responses": { + "http://file.storage.server": { + "/cases_delete.csv": { + "method": "GET", + "response": "id\n5002o00002BT0IUAA1" + } + }, + "https://test.salesforce.com": { + "/services/async/46.0/job": { + "method": "POST", + "response": " 7502o00000JrBezAAF delete Case 0052o000008w0iqAAA 2019-09-23T08:16:04.000Z 2019-09-23T08:16:04.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JrBezAAF/batch": { + "method": "POST", + "response": " 7512o00000Qdvv6AAB 7502o00000JrBezAAF Queued 2019-09-23T08:16:04.000Z 2019-09-23T08:16:04.000Z 0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JrBezAAF/batch/7512o00000Qdvv6AAB": { + "method": "GET", + "response": " 7512o00000Qdvv6AAB 7502o00000JrBezAAF Completed 2019-09-23T08:16:04.000Z 2019-09-23T08:16:11.000Z 1 0 557 453 0 " + }, + "/services/async/46.0/job/7502o00000JrBezAAF/batch/7512o00000Qdvv6AAB/result": { + "method": "GET", + "header": { + "Content-Type": "text/csv" + }, + "response": "\"Id\",\"Success\",\"Created\",\"Error\"\n\"5002o00002BT0IUAA1\",\"true\",\"false\",\"\"" + } + } + } + } } \ No newline at end of file diff --git a/spec/actions/bulk_q.json b/spec/testData/bulk_q.json similarity index 98% rename from spec/actions/bulk_q.json rename to spec/testData/bulk_q.json index 743c878..62b3993 100644 --- a/spec/actions/bulk_q.json +++ b/spec/testData/bulk_q.json @@ -1,50 +1,50 @@ -{ - "bulkQuery": { - "message": { - "body": { - "query": "SELECT Id, CaseNumber, SuppliedName FROM Case" - }, - "id": "f4e0d892-2bd0-490f-a8c2-281ac0383ad8" - }, - "responses": { - "https://test.salesforce.com": { - "/services/async/46.0/job": { - "method": "POST", - "response": " 7502o00000JrsqyAAB query Case 0052o000008w0iqAAA 2019-09-26T13:29:12.000Z 2019-09-26T13:29:12.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JrsqyAAB/batch": { - "method": "POST", - "response": " 7512o00000QevBUAAZ 7502o00000JrsqyAAB Queued 2019-09-26T13:29:12.000Z 2019-09-26T13:29:12.000Z 0 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JrsqyAAB/batch/7512o00000QevBUAAZ": { - "method": "GET", - "response": " 7512o00000QevBUAAZ 7502o00000JrsqyAAB Completed 2019-09-26T13:29:12.000Z 2019-09-26T13:29:13.000Z 3 0 0 0 0 " - }, - "/services/async/46.0/job/7502o00000JrsqyAAB/batch/7512o00000QevBUAAZ/result": { - "method": "GET", - "header": { - "content-type": "application/xml" - }, - "response": "7522o000009ND4w" - }, - "/services/async/46.0/job/7502o00000JrsqyAAB/batch/7512o00000QevBUAAZ/result/7522o000009ND4w": { - "method": "GET", - "header": { - "content-type": "text/csv" - }, - "response": "\"Id\",\"CaseNumber\",\"SuppliedName\"\n\"5002o00002BIlsXAAT\",\"00001020\",\"\"\n\"5002o00002BIlsWAAT\",\"00001019\",\"\"\n\"5002o00002BIlsYAAT\",\"00001021\",\"\"" - }, - "/services/async/46.0/job/7502o00000JrsqyAAB": { - "method": "POST", - "response": "" - } - }, - "http://file.storage.server": { - "/file": { - "method": "PUT", - "response": "OK" - } - } - } - } -} \ No newline at end of file +{ + "bulkQuery": { + "message": { + "body": { + "query": "SELECT Id, CaseNumber, SuppliedName FROM Case" + }, + "id": "f4e0d892-2bd0-490f-a8c2-281ac0383ad8" + }, + "responses": { + "https://test.salesforce.com": { + "/services/async/46.0/job": { + "method": "POST", + "response": " 7502o00000JrsqyAAB query Case 0052o000008w0iqAAA 2019-09-26T13:29:12.000Z 2019-09-26T13:29:12.000Z Open Parallel CSV 0 0 0 0 0 0 0 46.0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JrsqyAAB/batch": { + "method": "POST", + "response": " 7512o00000QevBUAAZ 7502o00000JrsqyAAB Queued 2019-09-26T13:29:12.000Z 2019-09-26T13:29:12.000Z 0 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JrsqyAAB/batch/7512o00000QevBUAAZ": { + "method": "GET", + "response": " 7512o00000QevBUAAZ 7502o00000JrsqyAAB Completed 2019-09-26T13:29:12.000Z 2019-09-26T13:29:13.000Z 3 0 0 0 0 " + }, + "/services/async/46.0/job/7502o00000JrsqyAAB/batch/7512o00000QevBUAAZ/result": { + "method": "GET", + "header": { + "content-type": "application/xml" + }, + "response": "7522o000009ND4w" + }, + "/services/async/46.0/job/7502o00000JrsqyAAB/batch/7512o00000QevBUAAZ/result/7522o000009ND4w": { + "method": "GET", + "header": { + "content-type": "text/csv" + }, + "response": "\"Id\",\"CaseNumber\",\"SuppliedName\"\n\"5002o00002BIlsXAAT\",\"00001020\",\"\"\n\"5002o00002BIlsWAAT\",\"00001019\",\"\"\n\"5002o00002BIlsYAAT\",\"00001021\",\"\"" + }, + "/services/async/46.0/job/7502o00000JrsqyAAB": { + "method": "POST", + "response": "" + } + }, + "http://file.storage.server": { + "/": { + "method": "PUT", + "response": "OK" + } + } + } + } +} diff --git a/spec/actions/deleteObject.json b/spec/testData/deleteObject.json similarity index 95% rename from spec/actions/deleteObject.json rename to spec/testData/deleteObject.json index 82db915..923c385 100644 --- a/spec/actions/deleteObject.json +++ b/spec/testData/deleteObject.json @@ -1,40 +1,40 @@ -{ - "cases": [ - { - "id":"136f209f-ab46-4856-8fa8-96a116f91115", - "attachments": {}, - "body": { - "request": { - "sobject":"Account" - }, - "response": { "id": "5002o00002E8F3NAAV", "success": true, "errors": [] } - }, - "headers": {}, - "metadata": {} - }, - { - "id":"4f0b9859-1269-4133-a4bd-a55db66aee8d", - "attachments": {}, - "body": { - "request": { - "sobject":"Case" - }, - "response": { "id": "0014400001teZzRAAU", "success": true, "errors": [] } - }, - "headers": {}, - "metadata": {} - }, - { - "id":"ca94b45b-fc17-4509-a714-fb3d7f3b7cc4", - "attachments": {}, - "body": { - "request": { - "sobject":"dsfs__Recipient__c" - }, - "response": { "id": "a0E2R00000N2W4A", "success": true, "errors": [] } - }, - "headers": {}, - "metadata": {} - } - ] -} +{ + "cases": [ + { + "id":"136f209f-ab46-4856-8fa8-96a116f91115", + "attachments": {}, + "body": { + "request": { + "sobject":"Account" + }, + "response": { "id": "5002o00002E8F3NAAV", "success": true, "errors": [] } + }, + "headers": {}, + "metadata": {} + }, + { + "id":"4f0b9859-1269-4133-a4bd-a55db66aee8d", + "attachments": {}, + "body": { + "request": { + "sobject":"Case" + }, + "response": { "id": "0014400001teZzRAAU", "success": true, "errors": [] } + }, + "headers": {}, + "metadata": {} + }, + { + "id":"ca94b45b-fc17-4509-a714-fb3d7f3b7cc4", + "attachments": {}, + "body": { + "request": { + "sobject":"dsfs__Recipient__c" + }, + "response": { "id": "a0E2R00000N2W4A", "success": true, "errors": [] } + }, + "headers": {}, + "metadata": {} + } + ] +} diff --git a/spec/testData/expectedMetadataIn.json b/spec/testData/expectedMetadataIn.json deleted file mode 100644 index fc0cfb7..0000000 --- a/spec/testData/expectedMetadataIn.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "description": "Contact", - "properties": { - "ExtId__c": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:time", - "type": "string" - }, - "UpdatebleCreateble": { - "custom": false, - "default": null, - "readonly": false, - "required": true, - "title": "xsd:time", - "type": "string" - } - }, - "type": "object" -} diff --git a/spec/testData/expectedMetadataOut.json b/spec/testData/expectedMetadataOut.json deleted file mode 100644 index 9b8d75e..0000000 --- a/spec/testData/expectedMetadataOut.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "description": "Contact", - "properties": { - "ExtId__c": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:time", - "type": "string" - }, - "ID": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "tns:ID", - "type": "string" - }, - "UpdatebleCreateble": { - "custom": false, - "default": null, - "readonly": false, - "required": true, - "title": "xsd:time", - "type": "string" - }, - "boolean": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:boolean", - "type": "boolean" - }, - "date": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:date", - "type": "string" - }, - "dateTime": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:dateTime", - "type": "string" - }, - "double": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:double", - "type": "number" - }, - "int": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:int", - "type": "integer" - }, - "string": { - "custom": false, - "default": "Web", - "enum": [ - "Web", - "Phone Inquiry", - "Partner Referral", - "Purchased List", - "Other" - ], - "readonly": true, - "required": true, - "title": "xsd:string", - "type": "string" - }, - "time": { - "custom": false, - "default": null, - "readonly": true, - "required": true, - "title": "xsd:time", - "type": "string" - } - }, - "type": "object" -} diff --git a/spec/testData/objectDescriptionForMetadata.json b/spec/testData/objectDescriptionForMetadata.json deleted file mode 100644 index 3dc65e4..0000000 --- a/spec/testData/objectDescriptionForMetadata.json +++ /dev/null @@ -1,165 +0,0 @@ -{ - "fields": [ - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "tns:ID", - "name": "ID", - "nillable": false, - "custom": false, - "soapType": "tns:ID", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "xsd:boolean", - "name": "boolean", - "nillable": false, - "custom": false, - "soapType": "xsd:boolean", - "updateable": false - }, - { - "calculated": false, - "defaultValue": "Web", - "deprecatedAndHidden": false, - "label": "xsd:string", - "name": "string", - "nillable": false, - "custom": false, - "soapType": "xsd:string", - "type": "picklist", - "updateable": false, - "picklistValues": [ - { - "active": true, - "defaultValue": false, - "label": "Web", - "validFor": null, - "value": "Web" - }, - { - "active": true, - "defaultValue": false, - "label": "Phone Inquiry", - "validFor": null, - "value": "Phone Inquiry" - }, - { - "active": true, - "defaultValue": false, - "label": "Partner Referral", - "validFor": null, - "value": "Partner Referral" - }, - { - "active": true, - "defaultValue": false, - "label": "Purchased List", - "validFor": null, - "value": "Purchased List" - }, - { - "active": true, - "defaultValue": false, - "label": "Other", - "validFor": null, - "value": "Other" - } - ] - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "xsd:dateTime", - "name": "dateTime", - "nillable": false, - "custom": false, - "soapType": "xsd:dateTime", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "xsd:double", - "name": "double", - "nillable": false, - "custom": false, - "soapType": "xsd:double", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "xsd:int", - "name": "int", - "nillable": false, - "custom": false, - "soapType": "xsd:int", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "xsd:date", - "name": "date", - "nillable": false, - "custom": false, - "soapType": "xsd:date", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "xsd:time", - "name": "time", - "nillable": false, - "custom": false, - "soapType": "xsd:time", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": true, - "label": "xsd:time", - "name": "timeDeprecated", - "nillable": false, - "custom": false, - "soapType": "xsd:time", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": true, - "label": "xsd:time", - "name": "ExtId__c", - "nillable": false, - "custom": false, - "soapType": "xsd:time", - "updateable": false - }, - { - "calculated": false, - "defaultValue": null, - "deprecatedAndHidden": false, - "label": "xsd:time", - "name": "UpdatebleCreateble", - "nillable": false, - "custom": false, - "soapType": "xsd:time", - "updateable": true, - "createable": true - } - ], - "name": "Contact" -} diff --git a/spec/testData/objectsList.json b/spec/testData/objectsList.json deleted file mode 100644 index a4e3b5d..0000000 --- a/spec/testData/objectsList.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "encoding": "UTF-8", - "maxBatchSize": 200, - "sobjects": [ - { - "activateable": false, - "createable": true, - "custom": false, - "customSetting": false, - "deletable": true, - "deprecatedAndHidden": false, - "feedEnabled": true, - "keyPrefix": "001", - "label": "Account", - "labelPlural": "Accounts", - "layoutable": true, - "mergeable": true, - "name": "Account", - "queryable": true, - "replicateable": true, - "retrieveable": true, - "searchable": true, - "triggerable": true, - "undeletable": true, - "updateable": true, - "urls": { - "sobject": "/services/data/v25.0/sobjects/Account", - "describe": "/services/data/v25.0/sobjects/Account/describe", - "rowTemplate": "/services/data/v25.0/sobjects/Account/{ID}" - } - }, - { - "activateable": false, - "createable": true, - "custom": false, - "customSetting": false, - "deletable": true, - "deprecatedAndHidden": false, - "feedEnabled": false, - "keyPrefix": "02Z", - "label": "Account Contact Role", - "labelPlural": "Account Contact Role", - "layoutable": false, - "mergeable": false, - "name": "AccountContactRole", - "queryable": true, - "replicateable": true, - "retrieveable": true, - "searchable": false, - "triggerable": false, - "undeletable": false, - "updateable": true, - "urls": { - "sobject": "/services/data/v25.0/sobjects/AccountContactRole", - "describe": "/services/data/v25.0/sobjects/AccountContactRole/describe", - "rowTemplate": "/services/data/v25.0/sobjects/AccountContactRole/{ID}" - } - }] -} \ No newline at end of file diff --git a/spec/sfAccountMetadata.json b/spec/testData/sfAccountMetadata.json similarity index 100% rename from spec/sfAccountMetadata.json rename to spec/testData/sfAccountMetadata.json diff --git a/spec/sfDocumentMetadata.json b/spec/testData/sfDocumentMetadata.json similarity index 100% rename from spec/sfDocumentMetadata.json rename to spec/testData/sfDocumentMetadata.json diff --git a/spec/sfObjects.json b/spec/testData/sfObjects.json similarity index 100% rename from spec/sfObjects.json rename to spec/testData/sfObjects.json diff --git a/spec/triggers/entry.spec.js b/spec/triggers/entry.spec.js new file mode 100644 index 0000000..0cb2d8a --- /dev/null +++ b/spec/triggers/entry.spec.js @@ -0,0 +1,74 @@ +/* eslint-disable max-len */ +const sinon = require('sinon'); +const { expect } = require('chai'); +const nock = require('nock'); +const logger = require('@elastic.io/component-logger')(); +const testCommon = require('../common.js'); +const common = require('../../lib/common.js'); +const polling = require('../../lib/entry'); +const records = require('../testData/trigger.results.json'); +const metaModelDocumentReply = require('../testData/sfDocumentMetadata.json'); + +const configuration = { + secretId: testCommon.secretId, + object: 'Document', +}; +const message = { + body: {}, +}; +const snapshot = {}; +let emitter; + +describe('Polling trigger test', () => { + beforeEach(() => { + emitter = { + emit: sinon.spy(), + logger, + }; + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(3) + .reply(200, testCommon.secret); + nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects/Document/describe`) + .times(3) + .reply(200, metaModelDocumentReply); + }); + afterEach(() => { + nock.cleanAll(); + }); + + it('should be called with arg data five times', async () => { + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=SELECT%20Id%2C%20FolderId%2C%20IsDeleted%2C%20Name%2C%20DeveloperName%2C%20NamespacePrefix%2C%20ContentType%2C%20Type%2C%20IsPublic%2C%20BodyLength%2C%20Body%2C%20Url%2C%20Description%2C%20Keywords%2C%20IsInternalUseOnly%2C%20AuthorId%2C%20CreatedDate%2C%20CreatedById%2C%20LastModifiedDate%2C%20LastModifiedById%2C%20SystemModstamp%2C%20IsBodySearchable%2C%20LastViewedDate%2C%20LastReferencedDate%20FROM%20Document%20WHERE%20LastModifiedDate%20%3E%3D%201970-01-01T00%3A00%3A00.000Z%20ORDER%20BY%20LastModifiedDate%20ASC`) + .reply(200, { done: true, totalSize: 5, records }); + await polling.process.call(emitter, message, configuration, snapshot); + expect(emitter.emit.withArgs('data').callCount).to.be.equal(records.length); + expect(emitter.emit.withArgs('snapshot').callCount).to.be.equal(1); + expect(emitter.emit.withArgs('snapshot').getCall(0).args[1].previousLastModified).to.be.equal(records[records.length - 1].LastModifiedDate); + scope.done(); + }); + + it('should not be called with arg data and snapshot', async () => { + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=SELECT%20Id%2C%20FolderId%2C%20IsDeleted%2C%20Name%2C%20DeveloperName%2C%20NamespacePrefix%2C%20ContentType%2C%20Type%2C%20IsPublic%2C%20BodyLength%2C%20Body%2C%20Url%2C%20Description%2C%20Keywords%2C%20IsInternalUseOnly%2C%20AuthorId%2C%20CreatedDate%2C%20CreatedById%2C%20LastModifiedDate%2C%20LastModifiedById%2C%20SystemModstamp%2C%20IsBodySearchable%2C%20LastViewedDate%2C%20LastReferencedDate%20FROM%20Document%20WHERE%20LastModifiedDate%20%3E%3D%201970-01-01T00%3A00%3A00.000Z%20ORDER%20BY%20LastModifiedDate%20ASC`) + .reply(200, { done: true, totalSize: 0, records: [] }); + await polling + .process.call(emitter, message, configuration, snapshot); + expect(emitter.emit.withArgs('data').callCount).to.be.equal(0); + expect(emitter.emit.withArgs('snapshot').callCount).to.be.equal(0); + scope.done(); + }); + + it('should not be called with arg data', async () => { + const scope = nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=SELECT%20Id%2C%20FolderId%2C%20IsDeleted%2C%20Name%2C%20DeveloperName%2C%20NamespacePrefix%2C%20ContentType%2C%20Type%2C%20IsPublic%2C%20BodyLength%2C%20Body%2C%20Url%2C%20Description%2C%20Keywords%2C%20IsInternalUseOnly%2C%20AuthorId%2C%20CreatedDate%2C%20CreatedById%2C%20LastModifiedDate%2C%20LastModifiedById%2C%20SystemModstamp%2C%20IsBodySearchable%2C%20LastViewedDate%2C%20LastReferencedDate%20FROM%20Document%20WHERE%20LastModifiedDate%20%3E%202019-28-03T00%3A00%3A00.000Z%20ORDER%20BY%20LastModifiedDate%20ASC`) + .reply(200, { done: true, totalSize: 5, records: [] }); + snapshot.previousLastModified = '2019-28-03T00:00:00.000Z'; + await polling + .process.call(emitter, message, configuration, snapshot); + expect(emitter.emit.withArgs('data').callCount).to.be.equal(0); + expect(emitter.emit.withArgs('snapshot').callCount).to.be.equal(0); + scope.done(); + }); +}); diff --git a/spec/triggers/query.spec.js b/spec/triggers/query.spec.js new file mode 100644 index 0000000..de094d3 --- /dev/null +++ b/spec/triggers/query.spec.js @@ -0,0 +1,63 @@ +const chai = require('chai'); +const nock = require('nock'); +const sinon = require('sinon'); +const logger = require('@elastic.io/component-logger')(); + +const { expect } = chai; + +const common = require('../../lib/common.js'); +const testCommon = require('../common.js'); + +const queryObjects = require('../../lib/triggers/query.js'); + +describe('Query module: processTrigger', () => { + const context = { emit: sinon.spy(), logger }; + testCommon.configuration.query = 'select name, id from account where name = \'testtest\''; + const testReply = { + result: [ + { + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], + }; + const expectedQuery = 'select%20name%2C%20id%20from%20account%20where%20name%20%3D%20%27testtest%27'; + beforeEach(() => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .times(5) + .reply(200, testCommon.secret); + nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/query?q=${expectedQuery}`) + .reply(200, { done: true, totalSize: testReply.result.length, records: testReply.result }); + }); + + afterEach(() => { + nock.cleanAll(); + context.emit.resetHistory(); + }); + + it('Gets objects emitAll', async () => { + testCommon.configuration.outputMethod = 'emitAll'; + await queryObjects.process.call(context, {}, testCommon.configuration); + expect(context.emit.getCall(0).lastArg.body.records).to.deep.equal(testReply.result); + }); + + it('Gets objects emitIndividually', async () => { + testCommon.configuration.outputMethod = 'emitIndividually'; + await queryObjects.process.call(context, {}, testCommon.configuration); + expect(context.emit.getCall(0).lastArg.body).to.deep.equal(testReply.result[0]); + }); +}); diff --git a/spec/triggers/trigger.spec.js b/spec/triggers/trigger.spec.js deleted file mode 100644 index 020b92b..0000000 --- a/spec/triggers/trigger.spec.js +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable no-unused-vars */ -const sinon = require('sinon'); -const { expect } = require('chai'); -const jsforce = require('jsforce'); -const logger = require('@elastic.io/component-logger')(); - -const polling = require('../../lib/entry'); - -const configuration = { - apiVersion: '39.0', - oauth: { - instance_url: 'https://example.com', - refresh_token: 'refresh_token', - access_token: 'access_token', - }, - object: 'Contact', -}; -const message = { - body: {}, -}; -const snapshot = {}; -let emitter; -let conn; -const records = require('../testData/trigger.results.json'); - - -describe('Polling trigger test', () => { - beforeEach(() => { - emitter = { - emit: sinon.spy(), - logger, - }; - }); - - afterEach(() => { - sinon.restore(); - }); - - it('should be called with arg data five times', async () => { - conn = sinon.stub(jsforce, 'Connection').callsFake(() => { - const connStub = { - sobject() { - return connStub; - }, - on() { - return connStub; - }, - select() { - return connStub; - }, - where() { - return connStub; - }, - sort() { - return connStub; - }, - execute() { - return records; - }, - }; - return connStub; - }); - await polling - .process.call(emitter, message, configuration, snapshot); - expect(emitter.emit.withArgs('data').callCount).to.be.equal(records.length); - expect(emitter.emit.withArgs('snapshot').callCount).to.be.equal(1); - expect(emitter.emit.withArgs('snapshot').getCall(0).args[1].previousLastModified).to.be.equal(records[records.length - 1].LastModifiedDate); - }); - - it('should not be called with arg data and snapshot', async () => { - conn = sinon.stub(jsforce, 'Connection').callsFake(() => { - const connStub = { - sobject() { - return connStub; - }, - on() { - return connStub; - }, - select() { - return connStub; - }, - where() { - return connStub; - }, - sort() { - return connStub; - }, - execute() { - return []; - }, - }; - return connStub; - }); - await polling - .process.call(emitter, message, configuration, snapshot); - expect(emitter.emit.withArgs('data').callCount).to.be.equal(0); - expect(emitter.emit.withArgs('snapshot').callCount).to.be.equal(0); - }); - - it('should not be called with arg data', async () => { - conn = sinon.stub(jsforce, 'Connection').callsFake(() => { - const connStub = { - sobject() { - return connStub; - }, - on() { - return connStub; - }, - select() { - return connStub; - }, - where() { - return connStub; - }, - sort() { - return connStub; - }, - execute(cfg, processResults) { - return connStub; - }, - }; - return connStub; - }); - snapshot.previousLastModified = '2019-28-03T00:00:00.000Z'; - await polling - .process.call(emitter, message, configuration, snapshot); - expect(emitter.emit.withArgs('data').callCount).to.be.equal(0); - expect(emitter.emit.withArgs('snapshot').callCount).to.be.equal(0); - }); - - it('should be called with arg error', async () => { - conn = sinon.stub(jsforce, 'Connection').callsFake(() => { - const connStub = { - sobject() { - return connStub; - }, - on() { - return connStub; - }, - select() { - return connStub; - }, - where() { - return connStub; - }, - sort() { - return connStub; - }, - execute() { - return []; - }, - }; - return connStub; - }); - snapshot.previousLastModified = '2019-28-03T00:00:00.000Z'; - configuration.sizeOfPollingPage = 'test'; - await polling - .process.call(emitter, message, configuration, snapshot); - expect(emitter.emit.withArgs('error').callCount).to.be.equal(1); - expect(emitter.emit.withArgs('data').callCount).to.be.equal(0); - expect(emitter.emit.withArgs('snapshot').callCount).to.be.equal(0); - }); -}); diff --git a/spec/util.spec.js b/spec/util.spec.js new file mode 100644 index 0000000..c6e464b --- /dev/null +++ b/spec/util.spec.js @@ -0,0 +1,36 @@ +const { expect } = require('chai'); +const nock = require('nock'); +const { getSecret, refreshToken } = require('../lib/util'); +const testCommon = require('./common.js'); + +describe('util test', () => { + afterEach(() => { + nock.cleanAll(); + }); + it('should getSecret', async () => { + nock(process.env.ELASTICIO_API_URI) + .get(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}`) + .reply(200, testCommon.secret); + const result = await getSecret(testCommon, testCommon.secretId); + expect(result).to.eql(testCommon.secret.data.attributes); + }); + + it('should refreshToken', async () => { + nock(process.env.ELASTICIO_API_URI) + .post(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}/refresh`) + .reply(200, testCommon.secret); + const result = await refreshToken(testCommon, testCommon.secretId); + expect(result).to.eql(testCommon.secret.data.attributes.credentials.access_token); + }); + + it.skip('should refreshToken fail', async () => { + nock(process.env.ELASTICIO_API_URI) + .post(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}/refresh`) + .times(3) + .reply(500, {}) + .post(`/v2/workspaces/${process.env.ELASTICIO_WORKSPACE_ID}/secrets/${testCommon.secretId}/refresh`) + .reply(200, testCommon.secret); + const result = await refreshToken(testCommon, testCommon.secretId); + expect(result).to.eql(testCommon.secret.data.attributes.credentials.access_token); + }); +}); diff --git a/spec/verifyCredentials.spec.js b/spec/verifyCredentials.spec.js index c63022d..871af19 100644 --- a/spec/verifyCredentials.spec.js +++ b/spec/verifyCredentials.spec.js @@ -1,101 +1,67 @@ -/* eslint-disable consistent-return */ +const chaiAsPromised = require('chai-as-promised'); const chai = require('chai'); + +chai.use(chaiAsPromised); +const { expect } = chai; + const nock = require('nock'); const logger = require('@elastic.io/component-logger')(); +const common = require('../lib/common.js'); +const testCommon = require('./common'); const verify = require('../verifyCredentials'); -const { expect } = chai; -const BASE_URL = 'https://someselasforcenode.com'; -const path = '/services/data/v32.0/sobjects'; -const oauth = { - access_token: 'some-access-id', - instance_url: 'https://someselasforcenode.com', -}; let cfg; +const testReply = { + result: [ + { + Id: 'testObjId', + FolderId: 'xxxyyyzzz', + Name: 'NotVeryImportantDoc', + IsPublic: false, + Body: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/800px-Everest_kalapatthar.jpg', + ContentType: 'imagine/noHeaven', + }, + { + Id: 'testObjId', + FolderId: '123yyyzzz', + Name: 'VeryImportantDoc', + IsPublic: true, + Body: 'wikipedia.org', + ContentType: 'imagine/noHell', + }, + ], +}; describe('Verify Credentials', () => { - before(() => { - if (!process.env.OAUTH_CLIENT_ID) { - process.env.OAUTH_CLIENT_ID = 'some'; - } - - if (!process.env.OAUTH_CLIENT_SECRET) { - process.env.OAUTH_CLIENT_SECRET = 'some'; - } - }); - - it('should return verified false without credentials in cfg', () => { - cfg = {}; - verify.call({ logger }, cfg, (err, data) => { - expect(err).to.equal(null); - expect(data).to.deep.equal({ verified: false }); - }); - }); - - it('should return verified false for 401 answer', (done) => { - cfg = { oauth }; - nock(BASE_URL) - .get(path) - .reply(401, ''); - verify.call({ logger }, cfg, (err, data) => { - if (err) return done(err); - - expect(err).to.equal(null); - expect(data).to.deep.equal({ verified: false }); - done(); - }); - }); - - it('should return verified false for 403 answer', (done) => { - cfg = { oauth }; - nock(BASE_URL) - .get(path) - .reply(403, ''); - verify.call({ logger }, cfg, (err, data) => { - if (err) return done(err); - - expect(err).to.equal(null); - expect(data.verified).to.deep.equal(false); - done(); - }); - }); - - it('should return verified true for 200 answer', (done) => { - cfg = { oauth }; - nock(BASE_URL) - .get(path) - .reply(200, ''); - verify.call({ logger }, cfg, (err, data) => { - if (err) return done(err); - expect(err).to.equal(null); - expect(data).to.deep.equal({ verified: true }); - done(); - }); - }); - - it('should return error for 500 cases', (done) => { - cfg = { oauth }; - nock(BASE_URL) - .get(path) - .reply(500, 'Super Error'); - verify.call({ logger }, cfg, (err) => { - expect(err.message).to.equal('Salesforce respond with 500'); - done(); - }); + it('should return verified true for 200 answer', async () => { + cfg = { + oauth: { + access_token: 'accessToken', + undefined_params: { + instance_url: testCommon.instanceUrl, + }, + }, + }; + nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) + .reply(200, { done: true, totalSize: testReply.result.length, sobjects: testReply.result }); + const result = await verify.call({ logger }, cfg); + expect(result).to.deep.equal({ verified: true }); }); - it('should throwError', (done) => { - cfg = { oauth }; - nock(BASE_URL) - .get(path) - .replyWithError({ - message: 'something awful happened', - code: 'AWFUL_ERROR', - }); - verify.call({ logger }, cfg, (err) => { - expect(err.message).to.equal('something awful happened'); - done(); - }); + it('should throwError', async () => { + cfg = { + oauth: { + access_token: 'accessToken', + undefined_params: { + instance_url: testCommon.instanceUrl, + }, + }, + }; + nock(testCommon.instanceUrl, { encodedQueryParams: true }) + .get(`/services/data/v${common.globalConsts.SALESFORCE_API_VERSION}/sobjects`) + .reply(500); + await expect(verify.call({ logger }, cfg)).be.rejected; }); }); diff --git a/verifyCredentials.js b/verifyCredentials.js index d37692d..6f1fa41 100644 --- a/verifyCredentials.js +++ b/verifyCredentials.js @@ -1,62 +1,14 @@ -const request = require('request'); -const fs = require('fs'); - -const NOT_ENABLED_ERROR = 'Salesforce respond with this error: "The REST API is not enabled for this Organization."'; -const VERSION = 'v32.0'; - -if (fs.existsSync('.env')) { - // eslint-disable-next-line global-require - require('dotenv').config(); -} - -// eslint-disable-next-line consistent-return -module.exports = function verify(credentials, cb) { - const self = this; - // eslint-disable-next-line no-use-before-define - checkOauth2EnvarsPresence(); - - function checkResponse(err, response, body) { - if (err) { - return cb(err); - } - self.logger.info('Salesforce response was: %s %j', response.statusCode, body); - if (response.statusCode === 401) { - return cb(null, { verified: false }); - } - if (response.statusCode === 403) { - return cb(null, { verified: false, details: NOT_ENABLED_ERROR }); - } - if (response.statusCode !== 200) { - return cb(new Error(`Salesforce respond with ${response.statusCode}`)); - } - return cb(null, { verified: true }); - } - self.logger.debug(credentials); - if (!credentials.oauth || credentials.oauth.error) { - return cb(null, { verified: false }); +const { callJSForceMethod } = require('./lib/helpers/wrapper'); + +module.exports = async function verify(credentials) { + try { + this.logger.trace('Incoming credentials: %j', credentials); + this.logger.info('Going to make request describeGlobal() for verifying credentials...'); + const result = await callJSForceMethod.call(this, credentials, 'describeGlobal'); + this.logger.info('Credentials are valid, it was found sobjects count: %s', result.sobjects.length); + return { verified: true }; + } catch (e) { + this.logger.error(e); + throw e; } - const token = credentials.oauth.access_token; - const url = `${credentials.oauth.instance_url}/services/data/${VERSION}/sobjects`; - - self.logger.info('To verify credentials send request to %s', url); - - const options = { - url, - headers: { - Authorization: `Bearer ${token}`, - }, - }; - - request.get(options, checkResponse); }; - -function checkOauth2EnvarsPresence() { - if (!process.env.OAUTH_CLIENT_ID) { - if (!process.env.OAUTH_CLIENT_SECRET) { - throw new Error('Environment variables are missed: OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET'); - } - throw new Error('Environment variables are missed: OAUTH_CLIENT_ID'); - } else if (!process.env.OAUTH_CLIENT_SECRET) { - throw new Error('Environment variables are missed: OAUTH_CLIENT_SECRET'); - } -}