From de937ea62ef54ebd0d407bff5b4f1b3671156cb2 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 24 May 2020 10:34:21 +0300 Subject: [PATCH 01/38] docs(webhooks): create webhooks API spec (#308) * docs(webhooks): create webhooks API spec --- docs/openapi3.yaml | 171 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 24d6ee70d..0f110e720 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -43,6 +43,9 @@ tags: - name: Benchmarks description: | By creating a benchmark for a specific test, each subsequent test run for that test will be given a score from 0-100 summarizing the test run in one simple to analyze numerical value. + - name: Webhooks + description: | + This resource allows you to configure webhooks. x-tagGroups: - name: Reference tags: @@ -54,6 +57,7 @@ x-tagGroups: - Configuration - Files - Benchmarks + - Webhooks paths: #DSL Definitions @@ -1347,6 +1351,124 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + # Webhooks + /v1/webhooks: + get: + operationId: retrieve-webhooks + tags: + - Webhooks + summary: Retrieve webhooks + description: Retrieve all webhooks. + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + post: + operationId: create-a-webhook + tags: + - Webhooks + summary: Create a Webhook + description: Create a new Webhook. + responses: + '201': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + description: The webhook to add + required: true + /v1/webhooks/{webhook_id}: + get: + operationId: retrieve-a-webhook + tags: + - Webhooks + summary: Retrieve a webhook by id + description: Retrieve a webhook by id. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + put: + operationId: update-a-webhook + tags: + - Webhooks + summary: Update a webhook + description: Update a webhook. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' # Files /v1/files: @@ -1913,7 +2035,8 @@ components: The event body will include detailed information about the test, such as the number of scenarios that were executed and the number of requests that were invoked. items: type: string - description: The url of where to send the webhook with the report information + format: uuid + description: The id of the webhook arrival_rate: type: number minimum: 1 @@ -2347,3 +2470,49 @@ components: filename: type: string description: the name of the file + type: number + description: benchmark percentage weight + webhook: + type: object + required: + - name + - webhook_url + - events + - format_type + properties: + id: + description: Unique webhook identifier + type: string + format: uuid + readOnly: true + name: + type: string + description: Webhook name + webhook_url: + type: string + description: Webhook url to post events + events: + description: list of events which will trigger the webhook + type: array + items: + $ref: '#/components/schemas/webhooks_types' + format_type: + $ref: '#/components/schemas/webhook_format_types' + global: + type: boolean + description: indicates whether the webhook should be applied globally(over all jobs) + webhooks_types: + type: string + enum: + - started + - api_failure + - aborted + - failed + - finished + - benchmark_passed + - benchmark_failed + webhook_format_types: + type: string + enum: + - slack + - json From 53f929087ce3b901e69016b58124169e5aad423b Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 15 Jun 2020 10:23:30 +0300 Subject: [PATCH 02/38] implement GET /webhooks (#318) --- .eslintrc.json | 4 +- docs/openapi3.yaml | 9 +- package-lock.json | 86 ++++++++--------- src/app.js | 2 + src/common/consts.js | 32 +++++++ .../migrations/04_webhooks.js | 62 ++++++++++++ src/database/sequlize-handler/sequlize.js | 3 + .../database/sequelize/sequelizeConnector.js | 29 +++--- .../controllers/webhooksController.js | 12 +++ .../database/cassandra/cassandraConnector.js | 16 ++++ .../models/database/databaseConnector.js | 21 ++++ .../database/sequelize/sequelizeConnector.js | 69 ++++++++++++++ src/webhooks/models/webhookManager.js | 8 ++ src/webhooks/routes/webhooksRouter.js | 11 +++ .../jobs/sequelize/sequelizeConnector-test.js | 2 +- .../sequelize/sequelizeConnector-test.js | 95 +++++++++++++++++++ 16 files changed, 396 insertions(+), 65 deletions(-) create mode 100644 src/database/sequlize-handler/migrations/04_webhooks.js create mode 100644 src/webhooks/controllers/webhooksController.js create mode 100644 src/webhooks/models/database/cassandra/cassandraConnector.js create mode 100644 src/webhooks/models/database/databaseConnector.js create mode 100644 src/webhooks/models/database/sequelize/sequelizeConnector.js create mode 100644 src/webhooks/models/webhookManager.js create mode 100644 src/webhooks/routes/webhooksRouter.js create mode 100644 tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js diff --git a/.eslintrc.json b/.eslintrc.json index 3d06b8857..6f95b6671 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,8 @@ "plugins": [], "env": { "node": true, - "mocha": true + "mocha": true, + "es6": true }, "parserOptions": { "ecmaVersion": 8, @@ -20,4 +21,3 @@ "camelcase": ["warn"] } } - \ No newline at end of file diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 0f110e720..c616c3480 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -2031,8 +2031,9 @@ components: webhooks: type: array description: | - A URL to which an event will be sent when the test execution is completed. - The event body will include detailed information about the test, such as the number of scenarios that were executed and the number of requests that were invoked. + An array of webhooks ids, events will be fired to the coresponding webhooks according to their events configuration. + The event body will include detailed information about the test, such as the number of scenarios that were executed + and the number of requests that were invoked. items: type: string format: uuid @@ -2476,7 +2477,7 @@ components: type: object required: - name - - webhook_url + - url - events - format_type properties: @@ -2488,7 +2489,7 @@ components: name: type: string description: Webhook name - webhook_url: + url: type: string description: Webhook url to post events events: diff --git a/package-lock.json b/package-lock.json index e20d85af2..36cd88391 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ }, "@babel/generator": { "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fgenerator/-/generator-7.9.4.tgz", "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { @@ -101,7 +101,7 @@ }, "@babel/helper-validator-identifier": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", "dev": true }, @@ -149,13 +149,13 @@ }, "@babel/parser": { "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fparser/-/parser-7.9.4.tgz", "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", "dev": true }, "@babel/template": { "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftemplate/-/template-7.8.6.tgz", "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { @@ -166,7 +166,7 @@ }, "@babel/traverse": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftraverse/-/traverse-7.9.0.tgz", "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { @@ -200,7 +200,7 @@ }, "@babel/types": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftypes/-/types-7.9.0.tgz", "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { @@ -578,7 +578,7 @@ }, "@types/color-name": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/@types%2fcolor-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/long": { @@ -636,7 +636,7 @@ }, "acorn": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/acorn/-/acorn-7.1.1.tgz", "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, @@ -711,7 +711,7 @@ }, "ansi-colors": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "resolved": "http://npm.zooz.co:8083/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, @@ -754,7 +754,7 @@ }, "anymatch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { @@ -764,7 +764,7 @@ "dependencies": { "normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1191,7 +1191,7 @@ }, "binary-extensions": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, @@ -1339,7 +1339,7 @@ }, "braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "resolved": "http://npm.zooz.co:8083/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { @@ -1622,7 +1622,7 @@ }, "chokidar": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "resolved": "http://npm.zooz.co:8083/chokidar/-/chokidar-3.3.0.tgz", "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { @@ -1638,13 +1638,13 @@ "dependencies": { "is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-glob": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { @@ -1653,7 +1653,7 @@ }, "normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -4641,7 +4641,7 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { @@ -4906,7 +4906,7 @@ }, "fsevents": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "resolved": "http://npm.zooz.co:8083/fsevents/-/fsevents-2.1.2.tgz", "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true @@ -5714,7 +5714,7 @@ }, "he": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "resolved": "http://npm.zooz.co:8083/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, @@ -6144,7 +6144,7 @@ }, "is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { @@ -6192,7 +6192,7 @@ }, "is-finite": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", "dev": true }, @@ -6233,7 +6233,7 @@ }, "is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, @@ -6688,7 +6688,7 @@ }, "kind-of": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "resolved": "http://npm.zooz.co:8083/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "latest-version": { @@ -7123,7 +7123,7 @@ }, "minimist": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { @@ -7175,7 +7175,7 @@ }, "mocha": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-7.1.1.tgz", "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { @@ -7207,7 +7207,7 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -7216,7 +7216,7 @@ }, "chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -7227,7 +7227,7 @@ "dependencies": { "supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -7238,7 +7238,7 @@ }, "debug": { "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "resolved": "http://npm.zooz.co:8083/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { @@ -7247,7 +7247,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "resolved": "http://npm.zooz.co:8083/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { @@ -7261,7 +7261,7 @@ }, "log-symbols": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/log-symbols/-/log-symbols-3.0.0.tgz", "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { @@ -7270,7 +7270,7 @@ }, "mkdirp": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "resolved": "http://npm.zooz.co:8083/mkdirp/-/mkdirp-0.5.3.tgz", "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { @@ -7279,13 +7279,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/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", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-6.0.0.tgz", "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { @@ -7685,7 +7685,7 @@ }, "node-environment-flags": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "resolved": "http://npm.zooz.co:8083/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { @@ -7957,7 +7957,7 @@ }, "object.getownpropertydescriptors": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { @@ -8336,7 +8336,7 @@ }, "picomatch": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "resolved": "http://npm.zooz.co:8083/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, @@ -8700,7 +8700,7 @@ }, "readdirp": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "resolved": "http://npm.zooz.co:8083/readdirp/-/readdirp-3.2.0.tgz", "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { @@ -9093,7 +9093,7 @@ }, "rewire": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-5.0.0.tgz", "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { @@ -9975,7 +9975,7 @@ }, "streamsearch": { "version": "0.1.2", - "resolved": "http://npm.zooz.co:8083/streamsearch/-/streamsearch-0.1.2.tgz", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, "string-width": { @@ -10485,7 +10485,7 @@ }, "to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { @@ -11020,7 +11020,7 @@ }, "yargs-unparser": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "resolved": "http://npm.zooz.co:8083/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { diff --git a/src/app.js b/src/app.js index 1253e1e95..4414bfacc 100644 --- a/src/app.js +++ b/src/app.js @@ -10,6 +10,7 @@ let dslRouter = require('./tests/routes/dslRoute.js'); let testsRouter = require('./tests/routes/testsRoute.js'); let processorsRouter = require('./processors/routes/processorsRoute.js'); let filesRouter = require('./files/routes/filesRoute.js'); +let webhooksRouter = require('./webhooks/routes/webhooksRouter'); let swaggerValidator = require('express-ajv-swagger-validation'); let audit = require('express-requests-logger'); @@ -66,6 +67,7 @@ module.exports = async () => { app.use('/v1/tests', testsRouter); app.use('/v1/processors', processorsRouter); app.use('/v1/files', filesRouter); + app.use('/v1/webhooks', webhooksRouter); app.use('/', function (req, res, next) { res.redirect('/ui'); diff --git a/src/common/consts.js b/src/common/consts.js index cf27650e0..6fa046e31 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -1,7 +1,39 @@ +const EVENT_FORMAT_TYPE_SLACK = 'slack'; +const EVENT_FORMAT_TYPE_JSON = 'json'; +const WEBHOOK_EVENT_TYPE_STARTED = 'started'; +const WEBHOOK_EVENT_TYPE_FINISHED = 'finished'; +const WEBHOOK_EVENT_TYPE_API_FAILURE = 'api_failure'; +const WEBHOOK_EVENT_TYPE_ABORTED = 'aborted'; +const WEBHOOK_EVENT_TYPE_FAILED = 'failed'; +const WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED = 'benchmark_passed'; +const WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED = 'benchmark_failed'; + module.exports = { TEST_TYPE_BASIC: 'basic', TEST_TYPE_DSL: 'dsl', PROCESSOR_FUNCTIONS_KEYS: ['beforeScenario', 'afterScenario', 'beforeRequest', 'afterResponse'], + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + EVENT_FORMAT_TYPE_SLACK, + EVENT_FORMAT_TYPE_JSON, + EVENT_FORMAT_TYPES: [ + EVENT_FORMAT_TYPE_SLACK, + EVENT_FORMAT_TYPE_JSON + ], + WEBHOOK_EVENT_TYPES: [ + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED + ], ERROR_MESSAGES: { NOT_FOUND: 'Not found', DSL_DEF_ALREADY_EXIST: 'Definition already exists', diff --git a/src/database/sequlize-handler/migrations/04_webhooks.js b/src/database/sequlize-handler/migrations/04_webhooks.js new file mode 100644 index 000000000..88a3a4a8a --- /dev/null +++ b/src/database/sequlize-handler/migrations/04_webhooks.js @@ -0,0 +1,62 @@ +const Sequelize = require('sequelize'); +const uuid = require('uuid'); +const { WEBHOOK_EVENT_TYPES } = require('../../../common/consts'); + +const tableName = 'webhooks'; +const webhookEventsTableName = 'webhook_events'; +const webhookEventMappingTableName = 'webhook_events'; +const webhookJobsMappingTableName = 'webhook_job_mapping'; +const columns = [ + { + name: 'name', + dt: Sequelize.DataTypes.TEXT('medium') + }, + { + name: 'global', + dt: Sequelize.DataTypes.BOOLEAN + } +]; + +async function takeActionOnColumn(describedTable, newColumnName, existAsyncAction, notExistAsyncAction) { + if (describedTable[newColumnName]) { + return existAsyncAction(); + } + return notExistAsyncAction(); +} + +function createEnumRow(name) { + return { + name, + id: uuid(), + created_at: new Date(), + updated_at: new Date() + }; +} + +module.exports.up = async (query, DataTypes) => { + let describedWebhooks = await query.describeTable(tableName); + const webhooksEventTypes = WEBHOOK_EVENT_TYPES.map(createEnumRow); + const promises = [ + ...columns.map(({ name, dt }) => + takeActionOnColumn( + describedWebhooks, + name, + () => null, + () => query.addColumn(tableName, name, dt) + ) + ) + ]; + await Promise.all(promises); + await query.bulkUpdate(tableName, { name: 'Webhook', global: false }); + await query.bulkInsert(webhookEventsTableName, webhooksEventTypes); +}; + +module.exports.down = async (query, DataTypes) => { + const promises = [ + ...columns.map(({ name }) => query.removeColumn(tableName, name)), + query.dropTable(webhookEventMappingTableName), + query.dropTable(webhookEventsTableName), + query.dropTable(webhookJobsMappingTableName) + ]; + await Promise.all(promises); +}; diff --git a/src/database/sequlize-handler/sequlize.js b/src/database/sequlize-handler/sequlize.js index 1af946d5c..ca05fa48d 100644 --- a/src/database/sequlize-handler/sequlize.js +++ b/src/database/sequlize-handler/sequlize.js @@ -10,11 +10,13 @@ const processorsSequlizeConnector = require('../../processors/models/database/se const fileSequlizeConnector = require('../../files/models/database/sequelize/sequelizeConnector'); const logger = require('../../../src/common/logger'); const databaseConfig = require('../../config/databaseConfig'); +const webhooksSequlizeConnector = require('../../webhooks/models/database/sequelize/sequelizeConnector'); const Sequelize = require('sequelize'); let sequlizeClient; module.exports.init = async () => { sequlizeClient = await createClient(); + await webhooksSequlizeConnector.init(sequlizeClient); await schedulerSequlizeConnector.init(sequlizeClient); await reportsSequlizeConnector.init(sequlizeClient); await testsSequlizeConnector.init(sequlizeClient); @@ -22,6 +24,7 @@ module.exports.init = async () => { await processorsSequlizeConnector.init(sequlizeClient); await fileSequlizeConnector.init(sequlizeClient); await runSequlizeMigrations(); + await sequlizeClient.sync(); }; module.exports.ping = async () => { diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index 6f4cab06f..c56c18c55 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -34,7 +34,7 @@ async function insertJob(jobId, jobInfo) { proxy_url: jobInfo.proxy_url, enabled: jobInfo.enabled, debug: jobInfo.debug, - webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { + webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { // still missing data attributes(name, global, format_type) return { id: uuid(), url: webhookUrl }; }) : undefined, emails: jobInfo.emails ? jobInfo.emails.map(emailAddress => { @@ -57,7 +57,7 @@ async function getJobsAndParse(jobId) { let options = { attributes: { exclude: ['updated_at', 'created_at'] }, - include: [job.webhook, job.email] + include: [job.email, 'webhooks'] }; if (jobId) { @@ -119,16 +119,6 @@ async function deleteJob(jobId) { } async function initSchemas() { - const webhook = client.define('webhook', { - id: { - type: Sequelize.DataTypes.UUID, - primaryKey: true - }, - url: { - type: Sequelize.DataTypes.STRING - } - }); - const email = client.define('email', { id: { type: Sequelize.DataTypes.UUID, @@ -181,10 +171,19 @@ async function initSchemas() { type: Sequelize.DataTypes.BOOLEAN } }); - - job.webhook = job.hasMany(webhook); job.email = job.hasMany(email); await job.sync(); - await webhook.sync(); await email.sync(); + + const webhooks = client.model('webhook'); + webhooks.belongsToMany(job, { + through: 'webhook_job_mapping', + as: 'jobs', + foreignKey: 'webhook_id' + }); + job.belongsToMany(webhooks, { + through: 'webhook_job_mapping', + as: 'webhooks', + foreignKey: 'job_id' + }); } \ No newline at end of file diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js new file mode 100644 index 000000000..aa068ad11 --- /dev/null +++ b/src/webhooks/controllers/webhooksController.js @@ -0,0 +1,12 @@ +'use strict'; +let webhookManager = require('../models/webhookManager'); + +module.exports.getAllWebhooks = async function (req, res, next) { + let webhooks; + try { + webhooks = await webhookManager.getAllWebhooks(); + return res.status(200).json(webhooks); + } catch (err) { + return next(err); + } +}; diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js new file mode 100644 index 000000000..b22b54435 --- /dev/null +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -0,0 +1,16 @@ +let logger = require('../../../../common/logger'); + +module.exports = { + init, + getAllWebhooks +}; + +async function init() { + const errorMessage = 'Webhooks feature is not implemented over Cassandra'; + logger.fatal(errorMessage); + throw new Error(errorMessage); +} + +async function getAllWebhooks() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js new file mode 100644 index 000000000..de1c28e3f --- /dev/null +++ b/src/webhooks/models/database/databaseConnector.js @@ -0,0 +1,21 @@ +let databaseConfig = require('../../../config/databaseConfig'); +let cassandraConnector = require('./cassandra/cassandraConnector'); +let sequelizeConnector = require('./sequelize/sequelizeConnector'); +let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +module.exports = { + init, + getAllWebhooks, + closeConnection +}; + +async function init() { + return databaseConnector.init(); +} + +function closeConnection() { + return databaseConnector.closeConnection(); +} + +async function getAllWebhooks(from, limit, exclude) { + return databaseConnector.getAllWebhooks(from, limit, exclude); +} \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js new file mode 100644 index 000000000..0045016b4 --- /dev/null +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -0,0 +1,69 @@ +'use strict'; + +const Sequelize = require('sequelize'); +let client; + +module.exports = { + init, + getAllWebhooks +}; + +async function init(sequelizeClient) { + client = sequelizeClient; + await initSchemas(); +} + +async function getAllWebhooks() { + const webhooksModel = client.model('webhook'); + return webhooksModel.findAll({ include: ['events'] }); +} + +async function initSchemas() { + const webhooksSchema = client.define('webhook', { + id: { + type: Sequelize.DataTypes.UUID, + primaryKey: true + }, + name: { + type: Sequelize.DataTypes.TEXT('medium') + }, + url: { + type: Sequelize.DataTypes.STRING + }, + global: { + type: Sequelize.DataTypes.BOOLEAN + }, + format_type: { + type: Sequelize.DataTypes.STRING + }, + created_at: { + type: Sequelize.DataTypes.DATE + }, + updated_at: { + type: Sequelize.DataTypes.DATE + } + }); + const webhooksEvents = client.define('webhook_event', { + id: { + type: Sequelize.DataTypes.UUID, + primaryKey: true + }, + name: { + type: Sequelize.DataTypes.TEXT('medium') + } + }); + + webhooksSchema.belongsToMany(webhooksEvents, { + through: 'webhook_event_mapping', + as: 'events', + foreignKey: 'webhook_id' + }); + webhooksEvents.belongsToMany(webhooksSchema, { + through: 'webhook_event_mapping', + as: 'webhooks', + foreignKey: 'webhook_event_id' + }); + + await webhooksSchema.sync(); + await webhooksEvents.sync(); +} diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js new file mode 100644 index 000000000..0fbe56dd9 --- /dev/null +++ b/src/webhooks/models/webhookManager.js @@ -0,0 +1,8 @@ +'use strict'; + +const databaseConnector = require('./database/databaseConnector'); + +module.exports.getAllWebhooks = async function () { + let getAllWebhooks = await databaseConnector.getAllWebhooks(); + return getAllWebhooks; +}; diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js new file mode 100644 index 000000000..9a5a24ec4 --- /dev/null +++ b/src/webhooks/routes/webhooksRouter.js @@ -0,0 +1,11 @@ +'use strict'; + +let swaggerValidator = require('express-ajv-swagger-validation'); +let express = require('express'); +let router = express.Router(); + +let webhooksController = require('../controllers/webhooksController'); + +router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); + +module.exports = router; diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index e96f8d394..16b5b1c0e 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -80,7 +80,7 @@ describe('Sequelize client tests', function () { describe('Init tests', () => { it('it should initialize sequelize with mysql client successfully', async () => { await sequelizeConnector.init(sequelizeStub()); - should(sequelizeDefineStub.calledThrice).eql(true); + should(sequelizeDefineStub.calledTwice).eql(true); }); }); diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js new file mode 100644 index 000000000..f0adad6a9 --- /dev/null +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -0,0 +1,95 @@ +'use strict'; +const sinon = require('sinon'), + { expect } = require('chai'), + databaseConfig = require('../../../../src/config/databaseConfig'), + sequelizeConnector = require('../../../../src/webhooks/models/database/sequelize/sequelizeConnector'); + +describe('Sequelize client tests', function () { + const webhookRaw = { + id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', + name: 'my special webhook', + url: 'http://callback.com', + global: false, + format_type: 'json', + created_at: '2020-06-13T13:13:16.763Z', + updated_at: '2020-06-13T13:13:16.763Z', + events: [] + }; + + let sandbox, + sequelizeModelStub, + sequelizeDeleteStub, + sequelizeDefineStub, + sequelizeGeValueStub, + sequelizeGetStub, + sequelizeCreateStub, + sequelizeUpdateStub, + sequelizeBelongsToMany; + + before(async () => { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(async () => { + databaseConfig.type = 'SQLITE'; + databaseConfig.name = 'predator'; + databaseConfig.username = 'username'; + databaseConfig.password = 'password'; + + sequelizeModelStub = sandbox.stub(); + sequelizeDefineStub = sandbox.stub(); + sequelizeGetStub = sandbox.stub(); + sequelizeDeleteStub = sandbox.stub(); + sequelizeGeValueStub = sandbox.stub(); + sequelizeCreateStub = sandbox.stub(); + sequelizeUpdateStub = sandbox.stub(); + sequelizeBelongsToMany = sandbox.stub(); + + sequelizeDefineStub.returns({ + hasMany: () => { + }, + sync: () => { + }, + belongsToMany: () => { + + } + }); + + sequelizeModelStub.returns({ + key: {}, + value: {}, + findAll: sequelizeGetStub, + findOne: sequelizeGeValueStub, + destroy: sequelizeDeleteStub, + create: sequelizeCreateStub + }); + + await sequelizeConnector.init({ + model: sequelizeModelStub, + define: sequelizeDefineStub + }); + }); + + afterEach(() => { + sandbox.resetHistory(); + }); + + after(() => { + sandbox.restore(); + }); + + describe('getAllWebhooks', function() { + describe('Happy flow', function() { + it('expect return an array with 1 webhook', async function() { + sequelizeGetStub.resolves([webhookRaw]); + const webhooks = await sequelizeConnector.getAllWebhooks(); + + expect(sequelizeGetStub.calledOnce).to.be.equal(true); + expect(sequelizeGetStub.args[0][0]).to.be.deep.equal({ include: ['events'] }); + + expect(webhooks).to.be.an('array').and.have.lengthOf(1); + expect(webhooks[0]).to.be.deep.equal(webhookRaw); + }); + }); + }); +}); From c8ea4e288832b197a01645fa7b6e8a4b2e339201 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 22 Jun 2020 11:50:59 +0300 Subject: [PATCH 03/38] feat(webhooks): implement POST /webhooks (#319) --- .eslintrc.json | 2 +- docs/openapi3.yaml | 2 + package-lock.json | 526 +++++++++++++----- package.json | 2 +- .../controllers/webhooksController.js | 10 + .../database/cassandra/cassandraConnector.js | 5 + .../models/database/databaseConnector.js | 5 + .../database/sequelize/sequelizeConnector.js | 38 +- src/webhooks/models/webhookManager.js | 12 + src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 41 ++ .../webhooks/webhooks-test.js | 142 +++++ .../jobs/sequelize/sequelizeConnector-test.js | 6 +- .../sequelize/sequelizeConnector-test.js | 60 +- 14 files changed, 704 insertions(+), 148 deletions(-) create mode 100644 tests/integration-tests/webhooks/helpers/requestCreator.js create mode 100644 tests/integration-tests/webhooks/webhooks-test.js diff --git a/.eslintrc.json b/.eslintrc.json index 6f95b6671..4c0ddf4ef 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 8, + "ecmaVersion": 2018, "sourceType": "module" }, "rules": { diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index c616c3480..200892a9e 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -2495,6 +2495,8 @@ components: events: description: list of events which will trigger the webhook type: array + uniqueItems: true + minItems: 1 items: $ref: '#/components/schemas/webhooks_types' format_type: diff --git a/package-lock.json b/package-lock.json index 36cd88391..0310fbb28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ }, "@babel/generator": { "version": "7.9.4", - "resolved": "http://npm.zooz.co:8083/@babel%2fgenerator/-/generator-7.9.4.tgz", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { @@ -101,7 +101,7 @@ }, "@babel/helper-validator-identifier": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", "dev": true }, @@ -149,13 +149,13 @@ }, "@babel/parser": { "version": "7.9.4", - "resolved": "http://npm.zooz.co:8083/@babel%2fparser/-/parser-7.9.4.tgz", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", "dev": true }, "@babel/template": { "version": "7.8.6", - "resolved": "http://npm.zooz.co:8083/@babel%2ftemplate/-/template-7.8.6.tgz", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { @@ -166,7 +166,7 @@ }, "@babel/traverse": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2ftraverse/-/traverse-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { @@ -200,7 +200,7 @@ }, "@babel/types": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2ftypes/-/types-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { @@ -635,9 +635,9 @@ } }, "acorn": { - "version": "7.1.1", - "resolved": "http://npm.zooz.co:8083/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", "dev": true }, "acorn-jsx": { @@ -711,7 +711,7 @@ }, "ansi-colors": { "version": "3.2.3", - "resolved": "http://npm.zooz.co:8083/ansi-colors/-/ansi-colors-3.2.3.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, @@ -754,7 +754,7 @@ }, "anymatch": { "version": "3.1.1", - "resolved": "http://npm.zooz.co:8083/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { @@ -764,7 +764,7 @@ "dependencies": { "normalize-path": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1191,7 +1191,7 @@ }, "binary-extensions": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/binary-extensions/-/binary-extensions-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, @@ -1339,7 +1339,7 @@ }, "braces": { "version": "3.0.2", - "resolved": "http://npm.zooz.co:8083/braces/-/braces-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { @@ -1622,7 +1622,7 @@ }, "chokidar": { "version": "3.3.0", - "resolved": "http://npm.zooz.co:8083/chokidar/-/chokidar-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { @@ -1638,13 +1638,13 @@ "dependencies": { "is-extglob": { "version": "2.1.1", - "resolved": "http://npm.zooz.co:8083/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-glob": { "version": "4.0.1", - "resolved": "http://npm.zooz.co:8083/is-glob/-/is-glob-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { @@ -1653,7 +1653,7 @@ }, "normalize-path": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1688,9 +1688,9 @@ "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==" }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, "cliui": { @@ -3987,22 +3987,22 @@ } }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz", + "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", @@ -4015,67 +4015,71 @@ "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", + "levn": "^0.4.1", "lodash": "^4.17.14", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.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", "v8-compile-cache": "^2.0.3" }, "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==", + "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": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "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": { - "color-convert": "^1.9.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, + "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 + }, "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": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "debug": { @@ -4087,6 +4091,27 @@ "ms": "^2.1.1" } }, + "eslint-utils": { + "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.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", + "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 + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4102,19 +4127,76 @@ "is-extglob": "^2.1.1" } }, + "levn": { + "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.2.1", + "type-check": "~0.4.0" + } + }, + "optionator": { + "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.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "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": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "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==", + "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-json-comments": { @@ -4124,12 +4206,30 @@ "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" + } + }, + "type-check": { + "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.2.1" + } + }, + "which": { + "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" } } } @@ -4349,9 +4449,9 @@ "dev": true }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4374,14 +4474,22 @@ "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", + "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", "dev": true, "requires": { - "acorn": "^7.1.1", + "acorn": "^7.2.0", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^1.2.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", + "dev": true + } } }, "esprima": { @@ -4390,18 +4498,18 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", - "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", + "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": "^5.0.0" + "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", - "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", "dev": true } } @@ -4641,7 +4749,7 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "http://npm.zooz.co:8083/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { @@ -4906,7 +5014,7 @@ }, "fsevents": { "version": "2.1.2", - "resolved": "http://npm.zooz.co:8083/fsevents/-/fsevents-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true @@ -5714,7 +5822,7 @@ }, "he": { "version": "1.2.0", - "resolved": "http://npm.zooz.co:8083/he/-/he-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, @@ -5976,9 +6084,9 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.2.0.tgz", + "integrity": "sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -6144,7 +6252,7 @@ }, "is-binary-path": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { @@ -6192,7 +6300,7 @@ }, "is-finite": { "version": "1.1.0", - "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", "dev": true }, @@ -6233,7 +6341,7 @@ }, "is-number": { "version": "7.0.0", - "resolved": "http://npm.zooz.co:8083/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, @@ -6688,7 +6796,7 @@ }, "kind-of": { "version": "6.0.3", - "resolved": "http://npm.zooz.co:8083/kind-of/-/kind-of-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "latest-version": { @@ -7123,7 +7231,7 @@ }, "minimist": { "version": "1.2.5", - "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.5.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { @@ -7175,7 +7283,7 @@ }, "mocha": { "version": "7.1.1", - "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-7.1.1.tgz", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { @@ -7207,7 +7315,7 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", - "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -7216,7 +7324,7 @@ }, "chalk": { "version": "2.4.2", - "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-2.4.2.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -7227,7 +7335,7 @@ "dependencies": { "supports-color": { "version": "5.5.0", - "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-5.5.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -7238,7 +7346,7 @@ }, "debug": { "version": "3.2.6", - "resolved": "http://npm.zooz.co:8083/debug/-/debug-3.2.6.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { @@ -7247,7 +7355,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "http://npm.zooz.co:8083/glob/-/glob-7.1.3.tgz", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { @@ -7261,7 +7369,7 @@ }, "log-symbols": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/log-symbols/-/log-symbols-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { @@ -7270,7 +7378,7 @@ }, "mkdirp": { "version": "0.5.3", - "resolved": "http://npm.zooz.co:8083/mkdirp/-/mkdirp-0.5.3.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { @@ -7279,13 +7387,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "http://npm.zooz.co:8083/ms/-/ms-2.1.1.tgz", + "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": "http://npm.zooz.co:8083/supports-color/-/supports-color-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { @@ -7685,7 +7793,7 @@ }, "node-environment-flags": { "version": "1.0.6", - "resolved": "http://npm.zooz.co:8083/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { @@ -7957,7 +8065,7 @@ }, "object.getownpropertydescriptors": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { @@ -8336,7 +8444,7 @@ }, "picomatch": { "version": "2.2.2", - "resolved": "http://npm.zooz.co:8083/picomatch/-/picomatch-2.2.2.tgz", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, @@ -8700,7 +8808,7 @@ }, "readdirp": { "version": "3.2.0", - "resolved": "http://npm.zooz.co:8083/readdirp/-/readdirp-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { @@ -9093,11 +9201,170 @@ }, "rewire": { "version": "5.0.0", - "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { "eslint": "^6.8.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 + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "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": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "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" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.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.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "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-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" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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" + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "right-pad": { @@ -9116,13 +9383,10 @@ } }, "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true }, "run-node": { "version": "1.0.0", @@ -10485,7 +10749,7 @@ }, "to-regex-range": { "version": "5.0.1", - "resolved": "http://npm.zooz.co:8083/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { @@ -10732,9 +10996,9 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "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 }, "valid-url": { @@ -11020,7 +11284,7 @@ }, "yargs-unparser": { "version": "1.6.0", - "resolved": "http://npm.zooz.co:8083/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { diff --git a/package.json b/package.json index a205d6ae7..3938b5a28 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "chai": "^4.2.0", "commitlint": "^8.1.0", "cz-conventional-changelog": "^2.1.0", - "eslint": "^6.2.2", + "eslint": "^7.2.0", "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-node": "^9.1.0", diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index aa068ad11..cae0a4cf7 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -10,3 +10,13 @@ module.exports.getAllWebhooks = async function (req, res, next) { return next(err); } }; + +module.exports.createWebhook = async function (req, res, next) { + let webhook; + try { + webhook = await webhookManager.createWebhook(req.body); + return res.status(201).json(webhook); + } catch (err) { + return next(err); + } +}; \ No newline at end of file diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index b22b54435..432a77fd3 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -2,6 +2,7 @@ let logger = require('../../../../common/logger'); module.exports = { init, + createWebhook, getAllWebhooks }; @@ -14,3 +15,7 @@ async function init() { async function getAllWebhooks() { throw new Error('Not implemented.'); } + +async function createWebhook() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index de1c28e3f..27e1949d9 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -5,6 +5,7 @@ let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cass module.exports = { init, getAllWebhooks, + createWebhook, closeConnection }; @@ -18,4 +19,8 @@ function closeConnection() { async function getAllWebhooks(from, limit, exclude) { return databaseConnector.getAllWebhooks(from, limit, exclude); +} + +async function createWebhook(webhook) { + return databaseConnector.createWebhook(webhook); } \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 0045016b4..1e10da838 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -1,11 +1,21 @@ 'use strict'; const Sequelize = require('sequelize'); +const uuid = require('uuid'); + let client; module.exports = { init, - getAllWebhooks + getAllWebhooks, + createWebhook +}; + +function parseWebhook(webhookRecord) { + return { + ...webhookRecord.dataValues, + events: webhookRecord.events && webhookRecord.events.map(eventRecord => eventRecord.dataValues.name) + }; }; async function init(sequelizeClient) { @@ -15,7 +25,31 @@ async function init(sequelizeClient) { async function getAllWebhooks() { const webhooksModel = client.model('webhook'); - return webhooksModel.findAll({ include: ['events'] }); + const webhooks = await webhooksModel.findAll({ include: ['events'] }); + return webhooks.map(parseWebhook); +} + +async function createWebhook(webhook) { + const id = uuid.v4(); + const webhooksModel = client.model('webhook'); + const webhooksEvents = client.model('webhook_event'); + const events = await webhooksEvents.findAll({ where: { name: webhook.events } }); + const eventsIds = events.map(({ id }) => id); + const webhookToInsert = { + id, + name: webhook.name, + url: webhook.url, + format_type: webhook.format_type, + global: webhook.global + }; + await client.transaction(async function(transaction) { + const createdWebhook = await webhooksModel.create(webhookToInsert, { transaction }); + await createdWebhook.setEvents(eventsIds, { transaction }); + return createdWebhook; + }); + const retrievedWebhook = await webhooksModel.findByPk(id, { include: ['events'] }); + const parsedWebhook = parseWebhook(retrievedWebhook); + return parsedWebhook; } async function initSchemas() { diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 0fbe56dd9..4d0133f02 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -2,7 +2,19 @@ const databaseConnector = require('./database/databaseConnector'); +const webhookDefaultValues = { + global: false +}; + module.exports.getAllWebhooks = async function () { let getAllWebhooks = await databaseConnector.getAllWebhooks(); return getAllWebhooks; }; + +module.exports.createWebhook = async function(webhookInfo) { + const webhook = { + ...webhookDefaultValues, + ...webhookInfo + }; + return databaseConnector.createWebhook(webhook); +}; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 9a5a24ec4..a9e59ab1e 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -7,5 +7,6 @@ let router = express.Router(); let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); +router.post('/', swaggerValidator.validate, webhooksController.createWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js new file mode 100644 index 000000000..0db7b19b5 --- /dev/null +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -0,0 +1,41 @@ + +const request = require('supertest'); +const expressApp = require('../../../../src/app'); + +let app; +const headers = { 'Content-Type': 'application/json' }; +const resourceUri = '/v1/webhooks'; + +module.exports = { + init, + createWebhook, + getWebhooks +}; + +async function init() { + try { + app = await expressApp(); + } catch (err){ + console.log(err); + process.exit(1); + } +} + +function createWebhook(body) { + return request(app) + .post(resourceUri) + .send(body) + .set(headers) + .expect(function(res){ + return res; + }); +} + +function getWebhooks() { + return request(app) + .get(resourceUri) + .set(headers) + .expect(function (res) { + return res; + }); +} \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js new file mode 100644 index 000000000..eb13793a9 --- /dev/null +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -0,0 +1,142 @@ +const { expect } = require('chai'); +const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); + +const webhookRequestSender = require('./helpers/requestCreator'); + +describe('Webhooks api', function () { + this.timeout(5000000); + before(async function () { + await webhookRequestSender.init(); + }); + + describe('Good requests', async function () { + describe('GET /v1/webhooks', async function () { + const numOfWebhooksToInsert = 5; + it(`return ${numOfWebhooksToInsert} webhooks`, async function() { + const webhooksToInsert = (new Array(numOfWebhooksToInsert)) + .fill(0, 0, numOfWebhooksToInsert) + .map(index => generateWebhook(`My webhook ${index}`)); + await Promise.all(webhooksToInsert.map(webhook => webhookRequestSender.createWebhook(webhook))); + + const webhooksGetResponse = await webhookRequestSender.getWebhooks(); + expect(webhooksGetResponse.statusCode).to.equal(200); + + const webhooks = webhooksGetResponse.body; + expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); + }); + }); + describe('POST /v1/webhooks', function () { + it('Create webhook and response 201 status code', async function() { + const webhook = generateWebhook(); + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + it('Create webhook with global=true and response 201 status code', async function() { + const webhook = generateWebhook(); + webhook.global = true; + + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + it('Create webhook with unspecified global field, expect default value and response 201 status code', async function() { + const webhook = generateWebhook(); + const { global, ...webhookWithoutGlobal } = webhook; + + let createWebhookResponse = await webhookRequestSender.createWebhook(webhookWithoutGlobal); + + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + }); + }); + + describe('Bad requests', function () { + describe('POST /v1/webhooks', function () { + describe('name validation', function() { + it('Create webhook with bad type of name', async function () { + const webhook = generateWebhook(5); + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('format_type validation', function() { + it('Create webhook with bad format_type', async function () { + const webhook = generateWebhook(); + webhook.format_type = 'TOTTALLY NOT A VALID FORMAT TYPE lalalalalla'; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('global validation', function() { + it('Create webhook with global not a boolean', async function () { + const webhook = generateWebhook(); + webhook.global = 'TOTTALLY NOT A VALID GLOBAL'; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('events validation', function() { + it('Create webhook with empty events', async function () { + const webhook = generateWebhook(); + webhook.events = []; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with invalid event name', async function () { + const webhook = generateWebhook('My special webhook', 'https://url.com/callback', ['bad_value']); + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with duplicate event name', async function () { + const webhook = generateWebhook(); + webhook.events = [webhook.events[0], webhook.events[0]]; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with too many valid values', async function () { + const webhook = generateWebhook(); + webhook.events = [...webhook.events, webhook.events[0]]; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + }); + }); +}); + +function generateWebhook(name = 'My webhook', url = 'https://humus.is.love/callback', events = WEBHOOK_EVENT_TYPES) { + return { + name, + url, + events, + global: false, + format_type: EVENT_FORMAT_TYPE_JSON + }; +} + +function assertDeepWebhookEquality(webhook, webhookFromAPI) { + const { + id, + events, + ...restOfWebhook + } = webhook; + const { + id: webhookFromAPIId, + events: webhookFromAPIEvents, + created_at: createdAt, + updated_at: updatedAt, + ...restwebhookFromAPI + } = webhookFromAPI; + expect(restOfWebhook).to.deep.equal(restwebhookFromAPI); + expect(events).to.have.members(webhookFromAPIEvents); +} \ No newline at end of file diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index 16b5b1c0e..a70371998 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -45,12 +45,14 @@ describe('Sequelize client tests', function () { hasMany: () => { }, sync: () => { - } + }, + belongsToMany: () => {} }); sequelizeModelStub.returns({ email: {}, webhook: {}, + belongsToMany: () => {}, create: sequelizeCreateStub, update: sequelizeUpdateStub, findAll: sequelizeGetStub, @@ -398,7 +400,7 @@ describe('Sequelize client tests', function () { }, 'include': [ {}, - {} + 'webhooks' ], 'where': { 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js index f0adad6a9..028e2d144 100644 --- a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -4,15 +4,20 @@ const sinon = require('sinon'), databaseConfig = require('../../../../src/config/databaseConfig'), sequelizeConnector = require('../../../../src/webhooks/models/database/sequelize/sequelizeConnector'); +const { WEBHOOK_EVENT_TYPES } = require('../../../../src/common/consts'); +const uuid = require('uuid'); + describe('Sequelize client tests', function () { const webhookRaw = { - id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', - name: 'my special webhook', - url: 'http://callback.com', - global: false, - format_type: 'json', - created_at: '2020-06-13T13:13:16.763Z', - updated_at: '2020-06-13T13:13:16.763Z', + dataValues: { + id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', + name: 'my special webhook', + url: 'http://callback.com', + global: false, + format_type: 'json', + created_at: '2020-06-13T13:13:16.763Z', + updated_at: '2020-06-13T13:13:16.763Z', + }, events: [] }; @@ -24,7 +29,8 @@ describe('Sequelize client tests', function () { sequelizeGetStub, sequelizeCreateStub, sequelizeUpdateStub, - sequelizeBelongsToMany; + sequelizeBelongsToMany, + sequelizeTransactionStub; before(async () => { sandbox = sinon.sandbox.create(); @@ -44,6 +50,7 @@ describe('Sequelize client tests', function () { sequelizeCreateStub = sandbox.stub(); sequelizeUpdateStub = sandbox.stub(); sequelizeBelongsToMany = sandbox.stub(); + sequelizeTransactionStub = sandbox.stub(); sequelizeDefineStub.returns({ hasMany: () => { @@ -61,12 +68,14 @@ describe('Sequelize client tests', function () { findAll: sequelizeGetStub, findOne: sequelizeGeValueStub, destroy: sequelizeDeleteStub, - create: sequelizeCreateStub + create: sequelizeCreateStub, + findByPk: sequelizeGetStub }); await sequelizeConnector.init({ model: sequelizeModelStub, - define: sequelizeDefineStub + define: sequelizeDefineStub, + transaction: sequelizeTransactionStub }); }); @@ -88,7 +97,36 @@ describe('Sequelize client tests', function () { expect(sequelizeGetStub.args[0][0]).to.be.deep.equal({ include: ['events'] }); expect(webhooks).to.be.an('array').and.have.lengthOf(1); - expect(webhooks[0]).to.be.deep.equal(webhookRaw); + expect(webhooks[0]).to.be.deep.contain(webhookRaw.dataValues); + expect(webhooks[0].events).to.be.deep.equal(webhookRaw.events); + }); + }); + }); + + describe('createWebhook', function() { + describe('Happy flow', function() { + it('expect to return a webhook with a valid uuid', async function() { + const events = [WEBHOOK_EVENT_TYPES[0], WEBHOOK_EVENT_TYPES[1]]; + const eventRecords = events.map(event => ({dataValues: {id: uuid(), name: event}})); + const webhook = { + ...webhookRaw.dataValues, + events + }; + const webhookWithEvents = { + dataValues: { + ...webhookRaw.dataValues + }, + events: eventRecords + }; + sequelizeGetStub.onFirstCall().resolves(eventRecords); + sequelizeGetStub.onSecondCall().resolves(webhookWithEvents); + sequelizeTransactionStub.resolves(); + const createdWebhook = await sequelizeConnector.createWebhook(webhook); + + expect(createdWebhook).to.be.an('object'); + expect(createdWebhook).to.be.deep.contain(webhookWithEvents.dataValues); + expect(createdWebhook.events).to.be.deep.equal(events); + expect(createdWebhook).to.have.a.property('id'); }); }); }); From 65cb79238a37a30f694d06c711b6d189d5f66393 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 22 Jun 2020 12:44:29 +0300 Subject: [PATCH 04/38] implement GET /webhooks/:webhook_id (#320) --- docs/openapi3.yaml | 18 ++++++++++ .../controllers/webhooksController.js | 11 ++++++ .../models/database/databaseConnector.js | 6 ++++ .../database/sequelize/sequelizeConnector.js | 11 ++++-- src/webhooks/models/webhookManager.js | 11 ++++++ src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 11 ++++++ .../webhooks/webhooks-test.js | 35 +++++++++++++++++++ 8 files changed, 101 insertions(+), 3 deletions(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 200892a9e..11933483b 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -1419,6 +1419,15 @@ paths: - Webhooks summary: Retrieve a webhook by id description: Retrieve a webhook by id. + parameters: + - in: path + name: webhook_id + description: The webhook id. + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 responses: '200': description: Success @@ -1444,6 +1453,15 @@ paths: - Webhooks summary: Update a webhook description: Update a webhook. + parameters: + - in: path + name: webhook_id + description: The webhook id. + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 responses: '200': description: Success diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index cae0a4cf7..7d7d8d323 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -11,6 +11,17 @@ module.exports.getAllWebhooks = async function (req, res, next) { } }; +module.exports.getWebhook = async function (req, res, next) { + let webhook; + let webhookId = req.params.webhook_id; + try { + webhook = await webhookManager.getWebhook(webhookId); + return res.status(200).json(webhook); + } catch (err) { + return next(err); + } +}; + module.exports.createWebhook = async function (req, res, next) { let webhook; try { diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 27e1949d9..3bae86155 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -2,10 +2,12 @@ let databaseConfig = require('../../../config/databaseConfig'); let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; + module.exports = { init, getAllWebhooks, createWebhook, + getWebhook, closeConnection }; @@ -23,4 +25,8 @@ async function getAllWebhooks(from, limit, exclude) { async function createWebhook(webhook) { return databaseConnector.createWebhook(webhook); +} + +async function getWebhook(webhookId) { + return databaseConnector.getWebhook(webhookId); } \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 1e10da838..e41678e34 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -1,5 +1,3 @@ -'use strict'; - const Sequelize = require('sequelize'); const uuid = require('uuid'); @@ -8,7 +6,8 @@ let client; module.exports = { init, getAllWebhooks, - createWebhook + createWebhook, + getWebhook }; function parseWebhook(webhookRecord) { @@ -29,6 +28,12 @@ async function getAllWebhooks() { return webhooks.map(parseWebhook); } +async function getWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + const webhook = await webhooksModel.findByPk(webhookId, { include: ['events'] }); + return parseWebhook(webhook); +} + async function createWebhook(webhook) { const id = uuid.v4(); const webhooksModel = client.model('webhook'); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 4d0133f02..6dc4bfa65 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -1,6 +1,7 @@ 'use strict'; const databaseConnector = require('./database/databaseConnector'); +const { ERROR_MESSAGES } = require('../../common/consts'); const webhookDefaultValues = { global: false @@ -11,6 +12,16 @@ module.exports.getAllWebhooks = async function () { return getAllWebhooks; }; +module.exports.getWebhook = async function (webhookId) { + const webhook = await databaseConnector.getWebhook(webhookId); + if (!webhook) { + const error = new Error(ERROR_MESSAGES.NOT_FOUND); + error.statusCode = 404; + throw error; + } + return webhook; +}; + module.exports.createWebhook = async function(webhookInfo) { const webhook = { ...webhookDefaultValues, diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index a9e59ab1e..79f23d48f 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -8,5 +8,6 @@ let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); +router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index 0db7b19b5..7c3330527 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -9,6 +9,8 @@ const resourceUri = '/v1/webhooks'; module.exports = { init, createWebhook, + getWebhooks, + getWebhook getWebhooks }; @@ -38,4 +40,13 @@ function getWebhooks() { .expect(function (res) { return res; }); +} + +function getWebhook(webhookdId) { + return request(app) + .get(`${resourceUri}/${webhookdId}`) + .set(headers) + .expect(function (res) { + return res; + }); } \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index eb13793a9..bc290f25c 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -1,4 +1,6 @@ const { expect } = require('chai'); +const uuid = require('uuid'); + const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); const webhookRequestSender = require('./helpers/requestCreator'); @@ -25,6 +27,19 @@ describe('Webhooks api', function () { expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); }); }); + describe('GET /webhook/:webhook_id', function () { + it('should retrieve the webhook that was created', async function() { + const webhook = generateWebhook(); + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(createWebhookResponse.statusCode).to.equal(201); + + const webhookId = createWebhookResponse.body.id; + const getWebhookResponse = await webhookRequestSender.getWebhook(webhookId); + + expect(getWebhookResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(webhook, getWebhookResponse.body); + }); + }); describe('POST /v1/webhooks', function () { it('Create webhook and response 201 status code', async function() { const webhook = generateWebhook(); @@ -111,6 +126,26 @@ describe('Webhooks api', function () { }); }); }); + describe('GET /webhook/:webhook_id', function () { + it('should return 400 for bad uuid', async function() { + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.getWebhook(badWebhookValue); + + expect(response.statusCode).to.equal(400); + }); + }); + }); + describe('Sad requests', function() { + describe('GET /webhook/:webhook_id', function () { + it('should return 404 for no existing webhook', async function() { + const notExistingWebhookId = uuid.v4(); + + const response = await webhookRequestSender.getWebhook(notExistingWebhookId); + + expect(response.statusCode).to.equal(404); + }); + }); }); }); From c057cb1e27baf4a38035d46e0f001b923922db1d Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 29 Jun 2020 20:49:57 +0300 Subject: [PATCH 05/38] feat(webhooks): implement DELETE /webhooks/:webhook_id (#325) --- docs/openapi3.yaml | 31 +++++++++++++++- .../database/sequelize/sequelizeConnector.js | 3 +- .../controllers/webhooksController.js | 10 +++++ .../models/database/databaseConnector.js | 7 +++- .../database/sequelize/sequelizeConnector.js | 17 +++++++-- src/webhooks/models/webhookManager.js | 4 ++ src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 18 ++++++--- .../webhooks/webhooks-test.js | 37 +++++++++++++++++-- .../sequelize/sequelizeConnector-test.js | 16 ++++++++ 10 files changed, 129 insertions(+), 15 deletions(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 11933483b..02987ae67 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -1487,6 +1487,35 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + delete: + operationId: delete-webhook + tags: + - Webhooks + summary: Delete webhook file + description: Delete a specific webhook by id. + parameters: + - in: path + name: webhook_id + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 + responses: + '204': + description: Success + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' # Files /v1/files: @@ -2489,8 +2518,6 @@ components: filename: type: string description: the name of the file - type: number - description: benchmark percentage weight webhook: type: object required: diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index c56c18c55..7ac38a393 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -179,7 +179,8 @@ async function initSchemas() { webhooks.belongsToMany(job, { through: 'webhook_job_mapping', as: 'jobs', - foreignKey: 'webhook_id' + foreignKey: 'webhook_id', + onDelete: 'CASCADE' }); job.belongsToMany(webhooks, { through: 'webhook_job_mapping', diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index 7d7d8d323..a957c8349 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -30,4 +30,14 @@ module.exports.createWebhook = async function (req, res, next) { } catch (err) { return next(err); } +}; + +module.exports.deleteWebhook = async function (req, res, next) { + let webhookId = req.params.webhook_id; + try { + await webhookManager.deleteWebhook(webhookId); + return res.status(204).json(); + } catch (err) { + return next(err); + } }; \ No newline at end of file diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 3bae86155..b63c53018 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -8,6 +8,7 @@ module.exports = { getAllWebhooks, createWebhook, getWebhook, + deleteWebhook, closeConnection }; @@ -29,4 +30,8 @@ async function createWebhook(webhook) { async function getWebhook(webhookId) { return databaseConnector.getWebhook(webhookId); -} \ No newline at end of file +} + +async function deleteWebhook(webhookId) { + return databaseConnector.deleteWebhook(webhookId); +}; \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index e41678e34..f526c9137 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -7,11 +7,12 @@ module.exports = { init, getAllWebhooks, createWebhook, - getWebhook + getWebhook, + deleteWebhook }; function parseWebhook(webhookRecord) { - return { + return webhookRecord && { ...webhookRecord.dataValues, events: webhookRecord.events && webhookRecord.events.map(eventRecord => eventRecord.dataValues.name) }; @@ -57,6 +58,15 @@ async function createWebhook(webhook) { return parsedWebhook; } +async function deleteWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + return webhooksModel.destroy({ + where: { + id: webhookId + } + }); +} + async function initSchemas() { const webhooksSchema = client.define('webhook', { id: { @@ -95,7 +105,8 @@ async function initSchemas() { webhooksSchema.belongsToMany(webhooksEvents, { through: 'webhook_event_mapping', as: 'events', - foreignKey: 'webhook_id' + foreignKey: 'webhook_id', + onDelete: 'CASCADE' }); webhooksEvents.belongsToMany(webhooksSchema, { through: 'webhook_event_mapping', diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 6dc4bfa65..2be6f9986 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -28,4 +28,8 @@ module.exports.createWebhook = async function(webhookInfo) { ...webhookInfo }; return databaseConnector.createWebhook(webhook); +}; + +module.exports.deleteWebhook = async function(webhookId) { + return databaseConnector.deleteWebhook(webhookId); }; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 79f23d48f..1e53ae499 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -9,5 +9,6 @@ let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); +router.delete('/:webhook_id', swaggerValidator.validate, webhooksController.deleteWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index 7c3330527..ae3d53ba0 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -1,4 +1,3 @@ - const request = require('supertest'); const expressApp = require('../../../../src/app'); @@ -10,8 +9,8 @@ module.exports = { init, createWebhook, getWebhooks, - getWebhook - getWebhooks + getWebhook, + deleteWebhook }; async function init() { @@ -42,9 +41,18 @@ function getWebhooks() { }); } -function getWebhook(webhookdId) { +function getWebhook(webhookId) { + return request(app) + .get(`${resourceUri}/${webhookId}`) + .set(headers) + .expect(function (res) { + return res; + }); +} + +function deleteWebhook(webhookId) { return request(app) - .get(`${resourceUri}/${webhookdId}`) + .delete(`${resourceUri}/${webhookId}`) .set(headers) .expect(function (res) { return res; diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index bc290f25c..f2df0f9f3 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -27,7 +27,7 @@ describe('Webhooks api', function () { expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); }); }); - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should retrieve the webhook that was created', async function() { const webhook = generateWebhook(); let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); @@ -66,6 +66,27 @@ describe('Webhooks api', function () { assertDeepWebhookEquality(webhook, createWebhookResponse.body); }); }); + describe('DELETE /v1/webhooks/:webhook_id', function() { + it('Create a webhook -> deleting it -> expecting to get 404 in GET', async function() { + const webhook = generateWebhook(); + + const insertWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertWebhookResponse.statusCode).to.equal(201); + const webhookId = insertWebhookResponse.body.id; + + const deleteWebhookResponse = await webhookRequestSender.deleteWebhook(webhookId); + expect(deleteWebhookResponse.statusCode).to.equal(204); + + const getWebhookResponse = await webhookRequestSender.getWebhook(webhookId); + expect(getWebhookResponse.statusCode).to.equal(404); + }); + it('Deleting unexisting webhook -> expect 204 status code', async function() { + const id = uuid.v4(); + + const deleteWebhookResponse = await webhookRequestSender.deleteWebhook(id); + expect(deleteWebhookResponse.statusCode).to.equal(204); + }); + }); }); describe('Bad requests', function () { @@ -126,18 +147,28 @@ describe('Webhooks api', function () { }); }); }); - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should return 400 for bad uuid', async function() { const badWebhookValue = 'lalallalalal'; const response = await webhookRequestSender.getWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); + }); + }); + describe('DELETE /v1/webhooks/:webhook_id', function () { + it('should return 400 for bad uuid', async function() { + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.deleteWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); }); }); }); + describe('Sad requests', function() { - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should return 404 for no existing webhook', async function() { const notExistingWebhookId = uuid.v4(); diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js index 028e2d144..5c5c4b004 100644 --- a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -130,4 +130,20 @@ describe('Sequelize client tests', function () { }); }); }); + describe('deleteWebhook', function() { + describe('Happy flow', function() { + it('expect to delete by query with proper webhook_id', async function() { + const id = uuid.v4(); + const queryOptions = { where: { id } }; + + sequelizeDeleteStub.resolves(); + await sequelizeConnector.deleteWebhook(id); + + expect(sequelizeDeleteStub.calledOnce).to.equal(true); + + const destroyOptions = sequelizeDeleteStub.args[0][0]; + expect(destroyOptions).to.deep.equal(queryOptions); + }); + }); + }); }); From 5213efb16dd0afe0fbb438b05ae46b856bc5a242 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 5 Jul 2020 13:22:53 +0300 Subject: [PATCH 06/38] feat(webhooks): implement PUT /webhooks/:webhook_id --- src/common/generateError.js | 6 + src/processors/models/processorsManager.js | 23 ++-- .../controllers/webhooksController.js | 10 ++ .../database/cassandra/cassandraConnector.js | 7 +- .../models/database/databaseConnector.js | 5 + .../database/sequelize/sequelizeConnector.js | 24 +++- src/webhooks/models/webhookManager.js | 29 +++-- src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 13 +- .../webhooks/webhooks-test.js | 111 +++++++++++++++++- 10 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 src/common/generateError.js diff --git a/src/common/generateError.js b/src/common/generateError.js new file mode 100644 index 000000000..411215b7e --- /dev/null +++ b/src/common/generateError.js @@ -0,0 +1,6 @@ + +module.exports = function(stautsCode, message) { + const error = new Error(message); + error.statusCode = stautsCode; + return error; +}; \ No newline at end of file diff --git a/src/processors/models/processorsManager.js b/src/processors/models/processorsManager.js index f5a853b52..2bbecec5b 100644 --- a/src/processors/models/processorsManager.js +++ b/src/processors/models/processorsManager.js @@ -5,12 +5,13 @@ const uuid = require('uuid'); const logger = require('../../common/logger'), databaseConnector = require('./database/databaseConnector'), testsManager = require('../../tests/models/manager'), - { ERROR_MESSAGES } = require('../../common/consts'); + { ERROR_MESSAGES } = require('../../common/consts'), + generateError = require('../../common/generateError'); module.exports.createProcessor = async function (processor) { const processorWithTheSameName = await databaseConnector.getProcessorByName(processor.name); if (processorWithTheSameName) { - throw generateError(ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST, 400); + throw generateError(400, ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST); } let processorId = uuid.v4(); try { @@ -36,7 +37,7 @@ module.exports.getProcessor = async function (processorId) { if (processor) { return processor; } else { - const error = generateError(ERROR_MESSAGES.NOT_FOUND, 404); + const error = generateError(404, ERROR_MESSAGES.NOT_FOUND); throw error; } }; @@ -46,7 +47,7 @@ module.exports.deleteProcessor = async function (processorId) { if (tests.length > 0) { let testNames = tests.map(test => test.name); let message = `${ERROR_MESSAGES.PROCESSOR_DELETION_FORBIDDEN}: ${testNames.join(', ')}`; - throw generateError(message, 409); + throw generateError(409, message); } return databaseConnector.deleteProcessor(processorId); }; @@ -54,12 +55,12 @@ module.exports.deleteProcessor = async function (processorId) { module.exports.updateProcessor = async function (processorId, processor) { const oldProcessor = await databaseConnector.getProcessorById(processorId); if (!oldProcessor) { - throw generateError(ERROR_MESSAGES.NOT_FOUND, 404); + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); } if (oldProcessor.name !== processor.name) { const processorWithUpdatedName = await databaseConnector.getProcessorByName(processor.name); if (processorWithUpdatedName) { - throw generateError(ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST, 400); + throw generateError(400, ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST); } } @@ -79,19 +80,13 @@ function verifyJSAndGetExportedFunctions(src) { let exports = m.exports; exportedFunctions = Object.keys(exports); } catch (err) { - let error = generateError('javascript syntax validation failed with error: ' + err.message, 422); + let error = generateError(422, 'javascript syntax validation failed with error: ' + err.message); throw error; } if (exportedFunctions.length === 0) { - let error = generateError('javascript has 0 exported functions', 422); + let error = generateError(422, 'javascript has 0 exported functions'); throw error; } return exportedFunctions; } - -function generateError(message, statusCode) { - const error = new Error(message); - error.statusCode = statusCode; - return error; -} diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index a957c8349..0def59d2d 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -40,4 +40,14 @@ module.exports.deleteWebhook = async function (req, res, next) { } catch (err) { return next(err); } +}; + +module.exports.updateWebhook = async function (req, res, next) { + let { body: updatedWebhook, params: { webhook_id: webhookId } } = req; + try { + const webhook = await webhookManager.updateWebhook(webhookId, updatedWebhook); + return res.status(200).json(webhook); + } catch (err) { + return next(err); + } }; \ No newline at end of file diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index 432a77fd3..844a2d71f 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -3,7 +3,8 @@ let logger = require('../../../../common/logger'); module.exports = { init, createWebhook, - getAllWebhooks + getAllWebhooks, + updateWebhook }; async function init() { @@ -19,3 +20,7 @@ async function getAllWebhooks() { async function createWebhook() { throw new Error('Not implemented.'); } + +async function updateWebhook() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index b63c53018..94f4f17b7 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -9,6 +9,7 @@ module.exports = { createWebhook, getWebhook, deleteWebhook, + updateWebhook, closeConnection }; @@ -34,4 +35,8 @@ async function getWebhook(webhookId) { async function deleteWebhook(webhookId) { return databaseConnector.deleteWebhook(webhookId); +}; + +async function updateWebhook(webhookId, webhook) { + return databaseConnector.updateWebhook(webhookId, webhook); }; \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index f526c9137..3ab471c20 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -8,6 +8,7 @@ module.exports = { getAllWebhooks, createWebhook, getWebhook, + updateWebhook, deleteWebhook }; @@ -18,6 +19,11 @@ function parseWebhook(webhookRecord) { }; }; +async function _getWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + return webhooksModel.findByPk(webhookId, { include: ['events'] }); +} + async function init(sequelizeClient) { client = sequelizeClient; await initSchemas(); @@ -30,8 +36,7 @@ async function getAllWebhooks() { } async function getWebhook(webhookId) { - const webhooksModel = client.model('webhook'); - const webhook = await webhooksModel.findByPk(webhookId, { include: ['events'] }); + const webhook = await _getWebhook(webhookId); return parseWebhook(webhook); } @@ -67,6 +72,21 @@ async function deleteWebhook(webhookId) { }); } +async function updateWebhook(webhookId, updatedWebhook) { + const webhooksModel = client.model('webhook'); + const webhooksEvents = client.model('webhook_event'); + + const oldWebhook = await _getWebhook(webhookId); + const newWebhookEvents = await webhooksEvents.findAll({ where: { name: updatedWebhook.events } }); + const newWebhookEventsIds = newWebhookEvents.map(({ id }) => id); + + await client.transaction(async function(transaction) { + await oldWebhook.setEvents(newWebhookEventsIds, { transaction }); + return webhooksModel.update(updatedWebhook, { where: { id: webhookId }, transaction }); + }); + return getWebhook(webhookId); +} + async function initSchemas() { const webhooksSchema = client.define('webhook', { id: { diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 2be6f9986..d40efad4c 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -2,27 +2,26 @@ const databaseConnector = require('./database/databaseConnector'); const { ERROR_MESSAGES } = require('../../common/consts'); +const generateError = require('../../common/generateError'); const webhookDefaultValues = { global: false }; -module.exports.getAllWebhooks = async function () { +async function getAllWebhooks() { let getAllWebhooks = await databaseConnector.getAllWebhooks(); return getAllWebhooks; }; -module.exports.getWebhook = async function (webhookId) { +async function getWebhook(webhookId) { const webhook = await databaseConnector.getWebhook(webhookId); if (!webhook) { - const error = new Error(ERROR_MESSAGES.NOT_FOUND); - error.statusCode = 404; - throw error; + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); } return webhook; }; -module.exports.createWebhook = async function(webhookInfo) { +async function createWebhook(webhookInfo) { const webhook = { ...webhookDefaultValues, ...webhookInfo @@ -30,6 +29,22 @@ module.exports.createWebhook = async function(webhookInfo) { return databaseConnector.createWebhook(webhook); }; -module.exports.deleteWebhook = async function(webhookId) { +async function deleteWebhook(webhookId) { return databaseConnector.deleteWebhook(webhookId); +}; + +async function updateWebhook(webhookId, webhook) { + const webhookInDB = await getWebhook(webhookId); + if (!webhookInDB) { + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); + } + return databaseConnector.updateWebhook(webhookId, webhook); +}; + +module.exports = { + getAllWebhooks, + getWebhook, + createWebhook, + deleteWebhook, + updateWebhook }; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 1e53ae499..098187c4c 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -10,5 +10,6 @@ router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); router.delete('/:webhook_id', swaggerValidator.validate, webhooksController.deleteWebhook); +router.put('/:webhook_id', swaggerValidator.validate, webhooksController.updateWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index ae3d53ba0..383421aea 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -10,7 +10,8 @@ module.exports = { createWebhook, getWebhooks, getWebhook, - deleteWebhook + deleteWebhook, + updateWebhook }; async function init() { @@ -57,4 +58,14 @@ function deleteWebhook(webhookId) { .expect(function (res) { return res; }); +} + +function updateWebhook(webhookId, webhook) { + return request(app) + .put(`${resourceUri}/${webhookId}`) + .send(webhook) + .set(headers) + .expect(function (res) { + return res; + }); } \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index f2df0f9f3..5e208637e 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -1,7 +1,7 @@ const { expect } = require('chai'); const uuid = require('uuid'); -const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); +const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPES, WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED } = require('../../../src/common/consts'); const webhookRequestSender = require('./helpers/requestCreator'); @@ -87,6 +87,95 @@ describe('Webhooks api', function () { expect(deleteWebhookResponse.statusCode).to.equal(204); }); }); + describe('PUT /v1/webhooks/:webhook_id', function() { + it('Insert a webhook -> update global -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, global: !webhook.global }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update url -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, url: `${webhook}/new/path` }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update format_type -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedFormatType = EVENT_FORMAT_TYPES.filter(format => format !== webhook.format_type)[0]; + const updatedWebhook = { ...webhook, format_type: updatedFormatType }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update name -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, name: webhook.name + ' added text' }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update events -> ensure it is updated', async function() { + const webhook = { + ...generateWebhook(), + events: [WEBHOOK_EVENT_TYPE_API_FAILURE] + }; + const updatedWebhook = { + ...webhook, + events: [WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED] + }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + }); }); describe('Bad requests', function () { @@ -162,6 +251,16 @@ describe('Webhooks api', function () { const response = await webhookRequestSender.deleteWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); + }); + }); + describe('PUT /v1/webhooks/:webhook_id', function() { + it('should return 400 for bad uuid', async function() { + const webhook = generateWebhook(); + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.updateWebhook(badWebhookValue, webhook); + expect(response.statusCode).to.equal(400); }); }); @@ -174,6 +273,16 @@ describe('Webhooks api', function () { const response = await webhookRequestSender.getWebhook(notExistingWebhookId); + expect(response.statusCode).to.equal(404); + }); + }); + describe('PUT /v1/webhooks/:webhook_id', function () { + it('should return 404 for no existing webhook', async function() { + const notExistingWebhookId = uuid.v4(); + const webhook = generateWebhook(); + + const response = await webhookRequestSender.updateWebhook(notExistingWebhookId, webhook); + expect(response.statusCode).to.equal(404); }); }); From d30f736be6e5628e1fcc15bdcb8ad73ccfb02d06 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sat, 25 Jul 2020 19:11:51 +0300 Subject: [PATCH 07/38] feat(webhooks): implement webhooks and jobs integrations --- .../database/cassandra/cassandraConnector.js | 7 +++- .../models/database/databaseConnector.js | 7 +++- .../database/sequelize/sequelizeConnector.js | 9 +++- src/webhooks/models/webhookManager.js | 41 ++++++++++++++++++- src/webhooks/models/webhooksFormatter.js | 14 +++++++ 5 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/webhooks/models/webhooksFormatter.js diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index 844a2d71f..d5334d324 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -4,7 +4,8 @@ module.exports = { init, createWebhook, getAllWebhooks, - updateWebhook + updateWebhook, + getAllGlobalWebhooks }; async function init() { @@ -24,3 +25,7 @@ async function createWebhook() { async function updateWebhook() { throw new Error('Not implemented.'); } + +async function getAllGlobalWebhooks() { + throw new Error('Not implemented.'); +} \ No newline at end of file diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 94f4f17b7..d0381acb0 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -10,6 +10,7 @@ module.exports = { getWebhook, deleteWebhook, updateWebhook, + getAllGlobalWebhooks, closeConnection }; @@ -39,4 +40,8 @@ async function deleteWebhook(webhookId) { async function updateWebhook(webhookId, webhook) { return databaseConnector.updateWebhook(webhookId, webhook); -}; \ No newline at end of file +}; + +async function getAllGlobalWebhooks() { + return databaseConnector.getAllGlobalWebhooks(); +} diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 3ab471c20..1563f2633 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -9,7 +9,8 @@ module.exports = { createWebhook, getWebhook, updateWebhook, - deleteWebhook + deleteWebhook, + getAllGlobalWebhooks }; function parseWebhook(webhookRecord) { @@ -40,6 +41,12 @@ async function getWebhook(webhookId) { return parseWebhook(webhook); } +async function getAllGlobalWebhooks() { + const webhooksModel = client.model('webhook'); + const webhooks = await webhooksModel.findAll({ include: ['events'], where: { global: true } }); + return webhooks.map(parseWebhook); +} + async function createWebhook(webhook) { const id = uuid.v4(); const webhooksModel = client.model('webhook'); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index d40efad4c..9ff5e0edd 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -3,6 +3,9 @@ const databaseConnector = require('./database/databaseConnector'); const { ERROR_MESSAGES } = require('../../common/consts'); const generateError = require('../../common/generateError'); +const requestSender = require('../../common/requestSender'); +const logger = require('../../common/logger'); +const webhooksFormatter = require('./webhooksFormatter'); const webhookDefaultValues = { global: false @@ -41,10 +44,46 @@ async function updateWebhook(webhookId, webhook) { return databaseConnector.updateWebhook(webhookId, webhook); }; +async function getAllGlobalWebhooks() { + return databaseConnector.getAllWebhooks(); +} + +async function fireSingleWebhook(webhook, payload) { + let webhookResponse = null; + try { + webhookResponse = await requestSender.send({ + method: 'POST', + url: webhook.url, + body: payload + }); + logger.info(`Webhook fired successfully, url=${webhook.url}`); + } catch (requestError) { + logger.error(`Webhook failed, url=${webhook.url}`); + throw requestError; + } + return webhookResponse; +} + +async function fireWebhooks(webhooks, payload) { + return webhooks.map(webhook => fireSingleWebhook(webhook, webhooksFormatter[webhook.format_type](payload))); +} + +async function fireWebhookByEvent(jobId, eventType, payload) { + const job = await getWebhook(jobId); + const globalWebhooks = await getAllGlobalWebhooks(); + const webhooks = [...job.webhooks, ...globalWebhooks]; + const webhooksWithEventType = webhooks.filter(webhook => webhook.events.include(eventType)); + if (webhooksWithEventType.length === 0) { + return; + } + await Promise.allSettled(fireWebhooks(webhooksWithEventType, payload)); +} + module.exports = { getAllWebhooks, getWebhook, createWebhook, deleteWebhook, - updateWebhook + updateWebhook, + fireWebhookByEvent }; \ No newline at end of file diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js new file mode 100644 index 000000000..b3b0f5801 --- /dev/null +++ b/src/webhooks/models/webhooksFormatter.js @@ -0,0 +1,14 @@ +const { EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK } = require('../../common/consts'); + +function json(payload) { + return payload; +} + +function slack(payload) { + // TODO: make it slacky +} + +module.exports = { + [EVENT_FORMAT_TYPE_JSON]: json, + [EVENT_FORMAT_TYPE_SLACK]: slack +}; From d2eaf7d265de459b5afff360e4166c818bca6c6a Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sat, 25 Jul 2020 22:23:29 +0300 Subject: [PATCH 08/38] fix(readme): fixed broken link for LICENSE.md --- LICENSE => LICENSE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE => LICENSE.md (100%) diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md From c2f88ca2f31a088938a6ca3c1fab610d11ddbc46 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sat, 8 Aug 2020 11:20:02 +0300 Subject: [PATCH 09/38] feat(webhooks): integrating the reports for webhooks --- .eslintrc.json | 2 +- src/common/consts.js | 9 +- src/reports/models/notifier.js | 145 +++++--------- .../models/statsFormatter.js | 0 src/webhooks/models/webhooksFormatter.js | 189 +++++++++++++++++- .../reporter/models/statsFormatter-test.js | 2 +- 6 files changed, 243 insertions(+), 104 deletions(-) rename src/{reports => webhooks}/models/statsFormatter.js (100%) diff --git a/.eslintrc.json b/.eslintrc.json index 4c0ddf4ef..01af938c7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,7 +13,7 @@ "rules": { "eol-last": "off", "space-before-function-paren": "off", - "indent": ["error", 4], + "indent": ["error", 4, { "SwitchCase": 1 }], "quotes": ["error", "single", { "avoidEscape": true }], "semi": ["error", "always",{ "omitLastInOneLineBlock": true}], "one-var": ["off"], diff --git a/src/common/consts.js b/src/common/consts.js index ebf0b2de2..beb5fe126 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -5,8 +5,11 @@ const WEBHOOK_EVENT_TYPE_FINISHED = 'finished'; const WEBHOOK_EVENT_TYPE_API_FAILURE = 'api_failure'; const WEBHOOK_EVENT_TYPE_ABORTED = 'aborted'; const WEBHOOK_EVENT_TYPE_FAILED = 'failed'; +const WEBHOOK_EVENT_TYPE_IN_PROGRESS = 'in_progress'; const WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED = 'benchmark_passed'; const WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED = 'benchmark_failed'; +const WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON = ':muscle:'; +const WEBHOOK_SLACK_DEFAULT_REPORTER_NAME = 'reporter'; module.exports = { TEST_TYPE_BASIC: 'basic', @@ -19,8 +22,11 @@ module.exports = { WEBHOOK_EVENT_TYPE_FAILED, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS, EVENT_FORMAT_TYPE_SLACK, EVENT_FORMAT_TYPE_JSON, + WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON, + WEBHOOK_SLACK_DEFAULT_REPORTER_NAME, EVENT_FORMAT_TYPES: [ EVENT_FORMAT_TYPE_SLACK, EVENT_FORMAT_TYPE_JSON @@ -32,7 +38,8 @@ module.exports = { WEBHOOK_EVENT_TYPE_ABORTED, WEBHOOK_EVENT_TYPE_FAILED, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, - WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS ], ERROR_MESSAGES: { NOT_FOUND: 'Not found', diff --git a/src/reports/models/notifier.js b/src/reports/models/notifier.js index 2b2670933..95f35ac17 100644 --- a/src/reports/models/notifier.js +++ b/src/reports/models/notifier.js @@ -3,14 +3,24 @@ const reportEmailSender = require('./reportEmailSender'), reportWebhookSender = require('./reportWebhookSender'), jobsManager = require('../../jobs/models/jobManager'), - statsFromatter = require('./statsFormatter'), + statsFromatter = require('../../webhooks/models/statsFormatter'), aggregateReportGenerator = require('./aggregateReportGenerator'), logger = require('../../common/logger'), constants = require('../utils/constants'), configHandler = require('../../configManager/models/configHandler'), reportUtil = require('../utils/reportUtil'), reportsManager = require('./reportsManager'), - configConstants = require('../../common/consts').CONFIG; + webhooksManager = require('../../webhooks/models/webhookManager'); +const { + CONFIG: configConstants, + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS +} = require('../../common/consts'); module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { let job; @@ -18,137 +28,86 @@ module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { try { job = await jobsManager.getJob(report.job_id); switch (stats.phase_status) { - case constants.SUBSCRIBER_FAILED_STAGE: - logger.info(metadata, stats.error, 'handling error message'); - await handleError(report, job, stats); - break; - case constants.SUBSCRIBER_STARTED_STAGE: - logger.info(metadata, 'handling started message'); - await handleStart(report, job); - break; - case constants.SUBSCRIBER_FIRST_INTERMEDIATE_STAGE: - logger.info(metadata, 'handling intermediate message'); - await handleFirstIntermediate(report, job); - break; - case constants.SUBSCRIBER_DONE_STAGE: - logger.info(metadata, 'handling done message'); - await handleDone(report, job, reportBenchmark); - break; - case constants.SUBSCRIBER_ABORTED_STAGE: - logger.info(metadata, 'handling aborted message'); - await handleAbort(report, job); - break; - default: - logger.trace(metadata, 'Handling unsupported test status: ' + JSON.stringify(stats)); - break; + case constants.SUBSCRIBER_FAILED_STAGE: { + logger.info(metadata, stats.error, 'handling error message'); + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FAILED, { report, stats }); + break; + } + case constants.SUBSCRIBER_STARTED_STAGE: { + logger.info(metadata, 'handling started message'); + await handleStart(report, job); + break; + } + case constants.SUBSCRIBER_FIRST_INTERMEDIATE_STAGE: { + logger.info(metadata, 'handling intermediate message'); + await handleFirstIntermediate(report, job); + break; + } + case constants.SUBSCRIBER_DONE_STAGE: { + logger.info(metadata, 'handling done message'); + await handleDone(report, job, reportBenchmark); + break; + } + case constants.SUBSCRIBER_ABORTED_STAGE: { + logger.info(metadata, 'handling aborted message'); + await handleAbort(report, job); + break; + } + default: { + logger.trace(metadata, 'Handling unsupported test status: ' + JSON.stringify(stats)); + break; + } } } catch (err) { logger.error(err, `Failed to notify for testId ${report.test_id} with reportID ${report.report_id}`); } }; -async function handleError(report, job, stats) { - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - - const webhookMessage = `😞 *Test with id: ${report.test_id} Failed*.\ntest configuration:\nenvironment: ${report.environment}\n${stats.data}`; - reportWebhookSender.send(webhooks, webhookMessage); -} - async function handleStart(report, job) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_STARTED_STAGE)) { return; } - - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - - let webhookMessage; - let rampToMessage = report.ramp_to ? `, ramp to: ${report.ramp_to} scenarios per second` : ''; - let parallelism = report.parallelism || 1; - webhookMessage = `🤓 *Test ${report.test_name} with id: ${report.test_id} has started*.\n - *test configuration:* environment: ${report.environment} duration: ${report.duration} seconds, arrival rate: ${report.arrival_rate} scenarios per second, number of runners: ${parallelism}${rampToMessage}`; - - reportWebhookSender.send(webhooks, webhookMessage); + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_STARTED, { report }); } async function handleFirstIntermediate(report, job) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_FIRST_INTERMEDIATE_STAGE)) { return; } - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - - let webhookMessage; + // WHAT DO WE DO WITH BATCHES let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - const phaseIndex = report.phase; - webhookMessage = `🤔 *Test ${report.test_name} with id: ${report.test_id} first batch of results arrived for phase ${phaseIndex}.*\n${statsFromatter.getStatsFormatted('intermediate', aggregatedReport.aggregate)}\n`; - if (report.grafana_report) { - webhookMessage += `<${report.grafana_report}|Track report in grafana dashboard>`; - } - reportWebhookSender.send(webhooks, webhookMessage); + webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_IN_PROGRESS, { report, aggregatedReport }); } async function handleDone(report, job, reportBenchmark) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_DONE_STAGE)) { return; } - let emails = await getEmailTargets(job); - let webhooks = await getWebhookTargets(job); const { benchmarkThreshold, benchmarkWebhook } = await getBenchmarkConfig(); - if (emails.length === 0 && webhooks.length === 0 && benchmarkWebhook.length === 0) { + if (emails.length === 0 && benchmarkWebhook.length === 0) { return; } let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - let webhookMessage = `😎 *Test ${report.test_name} with id: ${report.test_id} is finished.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; - - if (report.grafana_report) { - webhookMessage += `<${report.grafana_report}|View final grafana dashboard report>`; - } if (emails.length > 0) { reportEmailSender.sendAggregateReport(aggregatedReport, job, emails, reportBenchmark); } - if (webhooks.length > 0) { - reportWebhookSender.send(webhooks, webhookMessage); - } + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FINISHED, { report, aggregatedReport, reportBenchmark }); - if (benchmarkWebhook.length > 0) { - handleBenchmarkWebhookTreshhold(aggregatedReport, reportBenchmark.score, benchmarkThreshold, benchmarkWebhook); + if (reportBenchmark.score && benchmarkThreshold) { + const lastReports = await reportsManager.getReports(aggregatedReport.test_id); + const lastScores = lastReports.slice(0, 3).filter(report => report.score).map(report => report.score.toFixed(1)); + let event = reportBenchmark.score < benchmarkThreshold ? WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED : WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED; + await webhooksManager.fireWebhookByEvent(job.id, event, { aggregatedReport, score: reportBenchmark.score, lastScores, icon: ':sad_1:' }); } } async function handleAbort(report, job) { - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - let webhookMessage = `😢 *Test ${report.test_name} with id: ${report.test_id} was aborted.*\n`; - if (report.grafana_report) { - webhookMessage += `<${report.grafana_report}|View final grafana dashboard report>`; - } - reportWebhookSender.send(webhooks, webhookMessage); -} - -async function handleBenchmarkWebhookTreshhold(aggregatedReport, score, benchmarkThreshold, benchmarkWebhook) { - if (score && benchmarkThreshold && score < benchmarkThreshold) { - const lastReports = await reportsManager.getReports(aggregatedReport.test_id); - const lastScores = lastReports.slice(0, 3).filter(report => report.score).map(report => report.score.toFixed(1)); - let benchmarkWebhookMsg = `:sad_1: *Test ${aggregatedReport.test_name} got a score of ${score.toFixed(1)}` + - ` this is below the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + - `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; - reportWebhookSender.send([benchmarkWebhook], benchmarkWebhookMsg, { icon: ':sad_1:' }); - } + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_ABORTED, { report }); } async function getWebhookTargets(job) { diff --git a/src/reports/models/statsFormatter.js b/src/webhooks/models/statsFormatter.js similarity index 100% rename from src/reports/models/statsFormatter.js rename to src/webhooks/models/statsFormatter.js diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index b3b0f5801..237f6972d 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -1,14 +1,187 @@ -const { EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK } = require('../../common/consts'); +const { + EVENT_FORMAT_TYPE_JSON, + EVENT_FORMAT_TYPE_SLACK, + WEBHOOK_EVENT_TYPES, + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON, + WEBHOOK_SLACK_DEFAULT_REPORTER_NAME, + WEBHOOK_EVENT_TYPE_IN_PROGRESS +} = require('../../common/consts'); +const statsFromatter = require('./statsFormatter'); -function json(payload) { +function unknownWebhookEventTypeError(badWebhookEventTypeValue) { + return new Error(`Unrecognized webhook event: ${badWebhookEventTypeValue}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`); +} + +function slackWebhookFormat(message, options) { + return { + text: message, + icon_emoji: options.icon || WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON, + username: WEBHOOK_SLACK_DEFAULT_REPORTER_NAME + }; +} + +function json(event, testId, jobId, report, options) { + let payload = { + test_id: testId, + job_id: jobId, + event: event, + additional_details: {} + }; + let additionalDetails = {}; + switch (event) { + case WEBHOOK_EVENT_TYPE_STARTED: { + additionalDetails = { + test_name: , + environment: , + duration: , + arrival_rate: , + parallelism: , + ramp_to: rampTo, + }; + break; + } + case WEBHOOK_EVENT_TYPE_FINISHED: { + additionalDetails = { + test_name: , + grafana_report: , + aggregated_report: { + ...aggregatedReport.aggregate + }, + report_benchmark: { + ...reportBenchmark + } + }; + break; + } + case WEBHOOK_EVENT_TYPE_FAILED: { + additionalDetails = { + environment: , + stats: { + ...stats.data + } + }; + break; + } + case WEBHOOK_EVENT_TYPE_ABORTED: { + additionalDetails = { + testName, + grafanaReport: + }; + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { + additionalDetails = { + aggregated_test_name: , + benchmark_threshold: , + score: , + last_three_scores: , + aggregated_report: + }; + break; + } + case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + additionalDetails = { + + }; + break; + } + case WEBHOOK_EVENT_TYPE_API_FAILURE: { + additionalDetails = { + + }; + break; + } + default: { + throw unknownWebhookEventTypeError(); + } + } + payload.additional_details = additionalDetails; return payload; } -function slack(payload) { - // TODO: make it slacky +function slack(event, testId, jobId, report, options) { + let message = null; + const { + environment, + duration, + parallelism = 1, + ramp_to: rampTo, + arrival_rate: arrivalRate, + test_name: testName, + grafana_report: grafanaReport + } = report; + const { aggregatedReport, reportBenchmark } = options; + switch (event) { + case WEBHOOK_EVENT_TYPE_STARTED: { + let rampToMessage = rampTo ? `, ramp to: ${rampTo} scenarios per second` : ''; + message = `🤓 *Test ${testName} with id: ${testId} has started*.\n + *test configuration:* environment: ${environment} duration: ${duration} seconds, arrival rate: ${arrivalRate} scenarios per second, number of runners: ${parallelism}${rampToMessage}`; + break; + } + case WEBHOOK_EVENT_TYPE_FINISHED: { + message = `😎 *Test ${testName} with id: ${testId} is finished.*\n + ${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; + if (grafanaReport) { + message += `<${grafanaReport}|View final grafana dashboard report>`; + } + break; + } + case WEBHOOK_EVENT_TYPE_FAILED: { + message = `😞 *Test with id: ${testId} Failed*.\n + test configuration:\n + environment: ${environment}\n + ${stats.data}`; + break; + } + case WEBHOOK_EVENT_TYPE_ABORTED: { + message = `😢 *Test ${testName} with id: ${testId} was aborted.*\n`; + if (grafanaReport) { + message += `<${grafanaReport}|View final grafana dashboard report>`; + } + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { + message = `:sad_1: *Test ${aggregatedtestName} got a score of ${score.toFixed(1)}` + + ` this is below the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + + `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; + break; + } + case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + break; + } + case WEBHOOK_EVENT_TYPE_API_FAILURE: { + break; + } + default: { + throw unknownWebhookEventTypeError(); + } + } + return slackWebhookFormat(message); } -module.exports = { - [EVENT_FORMAT_TYPE_JSON]: json, - [EVENT_FORMAT_TYPE_SLACK]: slack -}; +module.exports = function(format, eventType, jobId, testId, report, options={}) { + switch(format) { + case EVENT_FORMAT_TYPE_SLACK: { + return slack(eventType, testId, jobId, report, options); + } + case EVENT_FORMAT_TYPE_JSON: { + return json(eventType, testId, jobId, report, options); + } + default: { + throw new Error(`Unrecognized webhook format: ${format}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`) + } + } +} \ No newline at end of file diff --git a/tests/unit-tests/reporter/models/statsFormatter-test.js b/tests/unit-tests/reporter/models/statsFormatter-test.js index 2e136da92..0e2e532f3 100644 --- a/tests/unit-tests/reporter/models/statsFormatter-test.js +++ b/tests/unit-tests/reporter/models/statsFormatter-test.js @@ -1,7 +1,7 @@ 'use strict'; let sinon = require('sinon'); let should = require('should'); -let statsFormatter = require('../../../../src/reports/models/statsFormatter'); +let statsFormatter = require('../../../../src/webhooks/models/statsFormatter'); const REPORT = { 'timestamp': '2018-05-15T14:20:02.109Z', From 1cc80c782949fe7512dee0695a9f5a4e21775667 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 9 Aug 2020 23:43:52 +0300 Subject: [PATCH 10/38] chore(dependencies): npm audit fix --- package-lock.json | 1131 +++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 586 insertions(+), 547 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12717e2c9..a79620faf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -154,20 +154,12 @@ "dev": true }, "@babel/runtime": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", - "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - } } }, "@babel/template": { @@ -227,26 +219,70 @@ } }, "@commitlint/cli": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-9.0.1.tgz", - "integrity": "sha512-BVOc/BY0FMmKTTH5oUVE0ukhPWDFf364FiYKk3GlXLOGTZPTXQ/9ncB2eMOaCF0PdcEVY4VoMjyoRSgcVapCMg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-9.1.2.tgz", + "integrity": "sha512-ctRrrPqjZ8r4Vc4FXpPaScEpkPwfvB0Us3NK2SD2AnLwXGMxOLFTabDmNySU1Xc40ud2CmJsaV8lpavvzs8ZZA==", "dev": true, "requires": { "@babel/runtime": "^7.9.6", - "@commitlint/format": "^9.0.1", - "@commitlint/lint": "^9.0.1", - "@commitlint/load": "^9.0.1", - "@commitlint/read": "^9.0.1", - "chalk": "3.0.0", + "@commitlint/format": "^9.1.2", + "@commitlint/lint": "^9.1.2", + "@commitlint/load": "^9.1.2", + "@commitlint/read": "^9.1.2", + "chalk": "4.1.0", "core-js": "^3.6.1", "get-stdin": "7.0.0", - "lodash": "^4.17.15", - "meow": "5.0.0", - "regenerator-runtime": "0.13.3", + "lodash": "^4.17.19", "resolve-from": "5.0.0", - "resolve-global": "1.0.0" + "resolve-global": "1.0.0", + "yargs": "^15.1.0" }, "dependencies": { + "@commitlint/execute-rule": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-9.1.2.tgz", + "integrity": "sha512-NGbeo0KCVYo1yj9vVPFHv6RGFpIF6wcQxpFYUKGIzZVV9Vz1WyiKS689JXa99Dt1aN0cZlEJJLnTNDIgYls0Vg==", + "dev": true + }, + "@commitlint/load": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-9.1.2.tgz", + "integrity": "sha512-FPL82xBuF7J3EJ57kLVoligQP4BFRwrknooP+vNT787AXmQ/Fddc/iYYwHwy67pNkk5N++/51UyDl/CqiHb6nA==", + "dev": true, + "requires": { + "@commitlint/execute-rule": "^9.1.2", + "@commitlint/resolve-extends": "^9.1.2", + "@commitlint/types": "^9.1.2", + "chalk": "4.1.0", + "cosmiconfig": "^6.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0" + } + }, + "@commitlint/resolve-extends": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-9.1.2.tgz", + "integrity": "sha512-HcoL+qFGmWEu9VM4fY0HI+VzF4yHcg3x+9Hx6pYFZ+r2wLbnKs964y0v68oyMO/mS/46MVoLNXZGR8U3adpadg==", + "dev": true, + "requires": { + "import-fresh": "^3.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + } + }, + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + }, + "ansi-regex": { + "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": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -257,16 +293,33 @@ "color-convert": "^2.0.1" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "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" } }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -288,12 +341,109 @@ "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", "dev": true }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "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 }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parse-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", + "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "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": "^5.0.0" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -302,6 +452,46 @@ "requires": { "has-flag": "^4.0.0" } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -315,30 +505,46 @@ } }, "@commitlint/ensure": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-9.0.1.tgz", - "integrity": "sha512-z8SEkfbn0lMnAtt7Hp3A8hE3CRCDsg+Eu3Xj1UJakOyCPJgHE1/vEyM2DO2dxTXVKuttiHeLDnUSHCxklm78Ng==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-9.1.2.tgz", + "integrity": "sha512-hwQICwpNSTsZgj/1/SdPvYAzhwjwgCJI4vLbT879+Jc+AJ6sj2bUDGw/F89vzgKz1VnaMm4D65bNhoWhG3pdhQ==", "dev": true, "requires": { - "@commitlint/types": "^9.0.1", - "lodash": "^4.17.15" + "@commitlint/types": "^9.1.2", + "lodash": "^4.17.19" + }, + "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + } } }, "@commitlint/execute-rule": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-9.0.1.tgz", "integrity": "sha512-fxnLadXs59qOBE9dInfQjQ4DmbGToQ0NjfqqmN6N8qS+KsCecO6N0mMUrC95et9xTeimFRr+0l9UMfmRVHNS/w==", - "dev": true + "dev": true, + "optional": true }, "@commitlint/format": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-9.0.1.tgz", - "integrity": "sha512-5oY7Jyve7Bfnx0CdbxFcpRKq92vUANFq3MVbz/ZTgvuYgUeMuYsSEwW6MJtOgOhHBQ2vZP/uPdxwmU+6pWZHcg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-9.1.2.tgz", + "integrity": "sha512-+ZWTOSGEU6dbn3NRh1q7sY5K5QLiSs7E2uSzuYnWHXcQk8nlTvnE0ibwMCQxdKLaOTZiN57fHM/7M9Re2gsRuw==", "dev": true, "requires": { - "chalk": "^3.0.0" + "@commitlint/types": "^9.1.2", + "chalk": "^4.0.0" }, "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -350,9 +556,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "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", @@ -392,33 +598,47 @@ } }, "@commitlint/is-ignored": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-9.0.1.tgz", - "integrity": "sha512-doGBfQgbsi48Hc48runGdN0TQFvf5XZizck8cylQdGG/3w+YwX9WkplEor7cvz8pmmuD6PpfpdukHSKlR8KmHQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-9.1.2.tgz", + "integrity": "sha512-423W/+Ro+Cc8cg81+t9gds1EscMZNjnGT31nKDvxVxJxXiXQsYYoFEQbU+nfUrRGQsUikEgEJ3ppVGr1linvcQ==", "dev": true, "requires": { - "@commitlint/types": "^9.0.1", - "semver": "7.1.3" + "@commitlint/types": "^9.1.2", + "semver": "7.3.2" }, "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + }, "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } }, "@commitlint/lint": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-9.0.1.tgz", - "integrity": "sha512-EAn4E6aGWZ96Dg9LN28kdELqkyFOAUGlXWmanMdWxGFGdOf24ZHzlVsbr/Yb1oSBUE2KVvAF5W2Mzn2+Ge5rOg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-9.1.2.tgz", + "integrity": "sha512-XvggqHZ4XSTKOgzJhCzz52cWRRO57QQnEviwGj0qnD4jdwC+8h2u9LNZwoa2tGAuaNM3nSm//wNK7FRZhgiiFA==", "dev": true, "requires": { - "@commitlint/is-ignored": "^9.0.1", - "@commitlint/parse": "^9.0.1", - "@commitlint/rules": "^9.0.1", - "@commitlint/types": "^9.0.1" + "@commitlint/is-ignored": "^9.1.2", + "@commitlint/parse": "^9.1.2", + "@commitlint/rules": "^9.1.2", + "@commitlint/types": "^9.1.2" + }, + "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + } } }, "@commitlint/load": { @@ -426,6 +646,7 @@ "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-9.0.1.tgz", "integrity": "sha512-6ix/pUjVAggmDLTcnpyk0bgY3H9UBBTsEeFvTkHV+WQ6LNIxsQk8SwEOEZzWHUqt0pxqMQeiUgYeSZsSw2+uiw==", "dev": true, + "optional": true, "requires": { "@commitlint/execute-rule": "^9.0.1", "@commitlint/resolve-extends": "^9.0.1", @@ -441,6 +662,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, + "optional": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -451,6 +673,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, + "optional": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -461,6 +684,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "optional": true, "requires": { "color-name": "~1.1.4" } @@ -469,13 +693,15 @@ "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 + "dev": true, + "optional": true }, "cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, + "optional": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.1.0", @@ -488,13 +714,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "dev": true, + "optional": true }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, + "optional": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -506,13 +734,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "dev": true, + "optional": true }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, + "optional": true, "requires": { "has-flag": "^4.0.0" } @@ -520,15 +750,15 @@ } }, "@commitlint/message": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-9.0.1.tgz", - "integrity": "sha512-9rKnOeBV5s5hnV895aE3aMgciC27kAjkV9BYVQOWRjZdXHFZxa+OZ94mkMp+Hcr61W++fox1JJpPiTuCTDX3TQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-9.1.2.tgz", + "integrity": "sha512-ndlx5z7bPVLG347oYJUHuQ41eTcsw+aUYT1ZwQyci0Duy2atpuoeeSw9SuM1PjufzRCpb6ExzFEgGzcCRKAJsg==", "dev": true }, "@commitlint/parse": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-9.0.1.tgz", - "integrity": "sha512-O39yMSMFdBtqwyM5Ld7RT6OGeI7jiXB9UUb09liIXIkltaZZo6CeoBD9hyfRWpaw81SiGL4OwHzp92mYVHLmow==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-9.1.2.tgz", + "integrity": "sha512-d+/VYbkotctW+lzDpus/R6xTerOqFQkW1myH+3PwnqYSE6JU/uHT4MlZNGJBv8pX9SPlR66t6X9puFobqtezEw==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", @@ -536,12 +766,12 @@ } }, "@commitlint/read": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-9.0.1.tgz", - "integrity": "sha512-EYbel85mAiHb56bS5jBJ71lEaGjTnkSJLxTV1u6dpxdSBkRdmAn2DSPd6KQSbwYGUlPCR+pAZeZItT1y0Xk3hg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-9.1.2.tgz", + "integrity": "sha512-C2sNBQOqeQXMxpWtRnXYKYB3D9yuybPtQNY/P67A6o8XH/UMHkFaUTyIx1KRgu0IG0yTTItRt46FGnsMWLotvA==", "dev": true, "requires": { - "@commitlint/top-level": "^9.0.1", + "@commitlint/top-level": "^9.1.2", "fs-extra": "^8.1.0", "git-raw-commits": "^2.0.0" }, @@ -564,6 +794,7 @@ "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-9.0.1.tgz", "integrity": "sha512-o6Lya2ILg1tEfWatS5x8w4ImvDzwb1whxsr2c/cxVCFqLF4hxHHHniZ0NJ+HFhYa1kBsYeKlD1qn9fHX5Y1+PQ==", "dev": true, + "optional": true, "requires": { "import-fresh": "^3.0.0", "lodash": "^4.17.15", @@ -572,27 +803,35 @@ } }, "@commitlint/rules": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-9.0.1.tgz", - "integrity": "sha512-K9IiQzF/C2tP/0mQUPSkOtmAEUleRQhZK1NFLVbsd6r4uobaczjPSYvEH+cuSHlD9b3Ori7PRiTgVBAZTH5ORQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-9.1.2.tgz", + "integrity": "sha512-1vecFuzqVqjiT57ocXq1bL8V6GEF1NZs3BR0dQzObaqHftImIxBVII299gasckTkcuxNc8M+7XxZyKxUthukpQ==", "dev": true, "requires": { - "@commitlint/ensure": "^9.0.1", - "@commitlint/message": "^9.0.1", - "@commitlint/to-lines": "^9.0.1", - "@commitlint/types": "^9.0.1" + "@commitlint/ensure": "^9.1.2", + "@commitlint/message": "^9.1.2", + "@commitlint/to-lines": "^9.1.2", + "@commitlint/types": "^9.1.2" + }, + "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + } } }, "@commitlint/to-lines": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-9.0.1.tgz", - "integrity": "sha512-FHiXPhFgGnvekF4rhyl1daHimEHkr81pxbHAmWG/0SOCehFr5THsWGoUYNNBMF7rdwUuVq4tXJpEOFiWBGKigg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-9.1.2.tgz", + "integrity": "sha512-o4zWcMf9EnzA3MOqx01780SgrKq5hqDJmUBPk30g6an0XcDuDy3OSZHHTJFdzsg4V9FjC4OY44sFeK7GN7NaxQ==", "dev": true }, "@commitlint/top-level": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-9.0.1.tgz", - "integrity": "sha512-AjCah5y7wu9F/hOwMnqsujPRWlKerX79ZGf+UfBpOdAh+USdV7a/UfQaqjgCzkxy5GcNO9ER5A+2mWrUHxJ0hQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-9.1.2.tgz", + "integrity": "sha512-KMPP5xVePcz3B1dKqcZdU4FZBVOkT+bG3ip4RQX2TeCJoomMkTjd0utALs7rpTGLID6BXbwwXepZCZJREjR/Bw==", "dev": true, "requires": { "find-up": "^4.0.0" @@ -638,7 +877,8 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.0.1.tgz", "integrity": "sha512-wo2rHprtDzTHf4tiSxavktJ52ntiwmg7eHNGFLH38G1of8OfGVwOc1sVbpM4jN/HK/rCMhYOi6xzoPqsv0537A==", - "dev": true + "dev": true, + "optional": true }, "@js-joda/core": { "version": "2.0.0", @@ -2394,26 +2634,80 @@ }, "dependencies": { "conventional-changelog-conventionalcommits": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.0.tgz", - "integrity": "sha512-oYHydvZKU+bS8LnGqTMlNrrd7769EsuEHKy4fh1oMdvvDi7fem8U+nvfresJ1IDB8K00Mn4LpiA/lR+7Gs6rgg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.1.tgz", + "integrity": "sha512-EQa7TJzF7H4EMkfjjJV7d+gragejDqa8NirZnCfRpruCMZqRbAJ8DqmYbkHrYtBYicXqgfM0zkk6HlvLPcyOdQ==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "lodash": "^4.17.15", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + } } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true } } }, "conventional-changelog-angular": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", - "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz", + "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + } } }, "conventional-changelog-atom": { @@ -2452,13 +2746,13 @@ } }, "conventional-changelog-core": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.1.7.tgz", - "integrity": "sha512-UBvSrQR2RdKbSQKh7RhueiiY4ZAIOW3+CSWdtKOwRv+KxIMNFKm1rOcGBFx0eA8AKhGkkmmacoTWJTqyz7Q0VA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.1.8.tgz", + "integrity": "sha512-M7VWA/RiVyjXIVt3SdfbgZFh0se67WBl78EzIYlBlFmDszzb00BwHlaNpgR1XrN0v56vtfBVq1sKEwQo2HbmkA==", "dev": true, "requires": { "add-stream": "^1.0.0", - "conventional-changelog-writer": "^4.0.16", + "conventional-changelog-writer": "^4.0.17", "conventional-commits-parser": "^3.1.0", "dateformat": "^3.0.0", "get-pkg-repo": "^1.0.0", @@ -2491,6 +2785,43 @@ "quick-lru": "^1.0.0" } }, + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "conventional-changelog-writer": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz", + "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.6", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^7.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^3.0.0" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -2513,6 +2844,23 @@ "through2": "^2.0.0" }, "dependencies": { + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -2531,6 +2879,12 @@ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", "dev": true }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2553,23 +2907,6 @@ "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", "dev": true }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" - } - }, "minimist-options": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", @@ -2651,6 +2988,12 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2711,92 +3054,48 @@ } }, "conventional-changelog-jshint": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.7.tgz", - "integrity": "sha512-qHA8rmwUnLiIxANJbz650+NVzqDIwNtc0TcpIa0+uekbmKHttidvQ1dGximU3vEDdoJVKFgR3TXFqYuZmYy9ZQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.8.tgz", + "integrity": "sha512-hB/iI0IiZwnZ+seYI+qEQ4b+EMQSEC8jGIvhO2Vpz1E5p8FgLz75OX8oB1xJWl+s4xBMB6f8zJr0tC/BL7YOjw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" - } - }, - "conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", - "dev": true - }, - "conventional-changelog-writer": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", - "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", - "dev": true, - "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.6", - "dateformat": "^3.0.0", - "handlebars": "^4.7.6", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^7.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, - "meow": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", - "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "requires": { - "@types/minimist": "^1.2.0", - "arrify": "^2.0.1", - "camelcase": "^6.0.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } + "is-obj": "^2.0.0" } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true } } }, + "conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", + "dev": true + }, "conventional-commit-types": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", @@ -4242,22 +4541,22 @@ } }, "eslint": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz", - "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.1.0", - "eslint-utils": "^2.0.0", - "eslint-visitor-keys": "^1.2.0", - "espree": "^7.1.0", - "esquery": "^1.2.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", @@ -4270,102 +4569,77 @@ "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", + "levn": "^0.3.0", "lodash": "^4.17.14", "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.8.3", "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "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==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "color-convert": "^1.9.0" } }, "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" - } - }, - "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==", + "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": { - "color-name": "~1.1.4" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "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 - }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "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" - } - }, - "eslint-utils": { - "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.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", - "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", - "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 + "dev": true, + "requires": { + "ms": "^2.1.1" + } }, "is-extglob": { "version": "2.1.1", @@ -4382,109 +4656,34 @@ "is-extglob": "^2.1.1" } }, - "levn": { - "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.2.1", - "type-check": "~0.4.0" - } - }, - "optionator": { - "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.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "shebang-command": { - "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": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "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": "^5.0.0" + "ansi-regex": "^4.1.0" } }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "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": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-check": { - "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.2.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "isexe": "^2.0.0" + "has-flag": "^3.0.0" } } } @@ -4729,22 +4928,14 @@ "dev": true }, "espree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", - "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^7.2.0", + "acorn": "^7.1.1", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.2.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", - "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", - "dev": true - } + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { @@ -5802,62 +5993,11 @@ "through2": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, "dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true - }, - "meow": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", - "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "arrify": "^2.0.1", - "camelcase": "^6.0.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } } } }, @@ -7636,149 +7776,48 @@ "integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo=" }, "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" }, "dependencies": { - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" - } - }, - "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" - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "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" - } - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", - "dev": true - }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.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 - }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", - "dev": true - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "dev": true, - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - } - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true - }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", "dev": true }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } } } } @@ -9715,9 +9754,9 @@ } }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, "regex-not": { diff --git a/package.json b/package.json index a7fa24e6d..3675cc6be 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "uuid": "^3.4.0" }, "devDependencies": { - "@commitlint/cli": "^9.0.1", + "@commitlint/cli": "^9.1.2", "@commitlint/config-conventional": "^9.0.1", "chai": "^4.2.0", "commitlint": "^9.0.1", From 4ee1eaeb3c58aa23fc577e7b1746118493e69e43 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 10 Aug 2020 19:45:57 +0300 Subject: [PATCH 11/38] feat(webhooks): reports fire webhooks by event --- docs/openapi3.yaml | 12 +- package.json | 1 + src/common/consts.js | 1 + .../database/sequelize/sequelizeConnector.js | 31 +- src/jobs/models/jobManager.js | 46 +- src/reports/models/notifier.js | 41 +- src/reports/models/reportWebhookSender.js | 30 -- .../database/sequelize/sequelizeConnector.js | 1 + src/webhooks/models/webhookManager.js | 32 +- src/webhooks/models/webhooksFormatter.js | 207 +++++---- tests/testExamples/Basic_test.json | 2 +- .../unit-tests/jobs/models/jobManager-test.js | 5 +- .../jobs/sequelize/sequelizeConnector-test.js | 426 ++++++++++-------- 13 files changed, 429 insertions(+), 406 deletions(-) delete mode 100644 src/reports/models/reportWebhookSender.js diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 7beb2b003..4d16564b8 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -598,6 +598,12 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + '422': + description: Unprocessable entity + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' '500': description: Internal server error content: @@ -2108,6 +2114,7 @@ components: example: 0 0 12 * * ? webhooks: type: array + uniqueItems: true description: | An array of webhooks ids, events will be fired to the coresponding webhooks according to their events configuration. The event body will include detailed information about the test, such as the number of scenarios that were executed @@ -2587,10 +2594,11 @@ components: type: string enum: - started + - finished + - in_progress - api_failure - - aborted - failed - - finished + - aborted - benchmark_passed - benchmark_failed webhook_format_types: diff --git a/package.json b/package.json index 3675cc6be..444c0589c 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/package", "name": "predator", "version": "1.4.0", "description": "Framework that manages the entire lifecycle of load testing a server, from creating test files, running scheduled and on-demand tests, and viewing test results.", diff --git a/src/common/consts.js b/src/common/consts.js index beb5fe126..614ea006a 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -2,6 +2,7 @@ const EVENT_FORMAT_TYPE_SLACK = 'slack'; const EVENT_FORMAT_TYPE_JSON = 'json'; const WEBHOOK_EVENT_TYPE_STARTED = 'started'; const WEBHOOK_EVENT_TYPE_FINISHED = 'finished'; +// TODO: how to recognize api_failure? const WEBHOOK_EVENT_TYPE_API_FAILURE = 'api_failure'; const WEBHOOK_EVENT_TYPE_ABORTED = 'aborted'; const WEBHOOK_EVENT_TYPE_FAILED = 'failed'; diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index 7ac38a393..a490a0593 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -34,22 +34,21 @@ async function insertJob(jobId, jobInfo) { proxy_url: jobInfo.proxy_url, enabled: jobInfo.enabled, debug: jobInfo.debug, - webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { // still missing data attributes(name, global, format_type) - return { id: uuid(), url: webhookUrl }; - }) : undefined, emails: jobInfo.emails ? jobInfo.emails.map(emailAddress => { return { id: uuid(), address: emailAddress }; }) : undefined }; let include = []; - if (params.webhooks) { - include.push({ association: job.webhook }); - } if (params.emails) { include.push({ association: job.email }); } - return job.create(params, { include }); + let createdJob = null; + await client.transaction(async function(transaction) { + createdJob = await job.create(params, { include, transaction }); + return createdJob.setWebhooks(jobInfo.webhooks || [], { transaction }); + }); + return createdJob; } async function getJobsAndParse(jobId) { @@ -68,7 +67,7 @@ async function getJobsAndParse(jobId) { allJobs.forEach(job => { job.emails = job.emails && job.emails.length > 0 ? job.emails.map(sqlJob => sqlJob.dataValues.address) : undefined; - job.webhooks = job.webhooks && job.webhooks.length > 0 ? job.webhooks.map(sqlJob => sqlJob.dataValues.url) : undefined; + job.webhooks = job.webhooks && job.webhooks.length > 0 ? job.webhooks.map(sqlJob => sqlJob.dataValues.id) : undefined; }); return allJobs; } @@ -105,9 +104,14 @@ async function updateJob(jobId, jobInfo) { id: jobId } }; - - let result = await job.update(params, options); - return result; + let updatedJob = null; + let oldJob = await job.findByPk(jobId); + await client.transaction(async function(transaction) { + updatedJob = await job.update(params, { ...options, transaction }); + await oldJob.setWebhooks(jobInfo.webhooks || [], { transaction }); + return updatedJob; + }); + return updatedJob; } async function deleteJob(jobId) { @@ -179,12 +183,11 @@ async function initSchemas() { webhooks.belongsToMany(job, { through: 'webhook_job_mapping', as: 'jobs', - foreignKey: 'webhook_id', - onDelete: 'CASCADE' + foreignKey: 'webhook_id' }); job.belongsToMany(webhooks, { through: 'webhook_job_mapping', as: 'webhooks', foreignKey: 'job_id' }); -} \ No newline at end of file +} diff --git a/src/jobs/models/jobManager.js b/src/jobs/models/jobManager.js index d4250bdef..154067987 100644 --- a/src/jobs/models/jobManager.js +++ b/src/jobs/models/jobManager.js @@ -137,26 +137,32 @@ module.exports.getJob = async (jobId) => { module.exports.updateJob = async (jobId, jobConfig) => { const configData = await configHandler.getConfig(); - return databaseConnector.updateJob(jobId, jobConfig) - .then(function () { - return databaseConnector.getJob(jobId) - .then(function (updatedJob) { - if (updatedJob.length === 0) { - let error = new Error('Not found'); - error.statusCode = 404; - throw error; - } - if (cronJobs[jobId]) { - cronJobs[jobId].stop(); - delete cronJobs[jobId]; - } - addCron(jobId, updatedJob[0], updatedJob[0].cron_expression, configData); - logger.info('Job updated successfully to database'); - }); - }).catch(function (err) { - logger.error(err, 'Error occurred trying to update job'); - return Promise.reject(err); - }); + let job = null; + let updatedJob = null; + [ job ] = await databaseConnector.getJob(jobId); + if (!job.cron_expression) { + let error = new Error('Can not update jobs from type run_immediately: true'); + error.statusCode = 422; + throw error; + } + try { + updatedJob = await databaseConnector.updateJob(jobId, jobConfig); + job = await databaseConnector.getJob(jobId); + } catch (err) { + logger.error(err, 'Error occurred trying to update job'); + throw err; + } + if (job.length === 0) { + let error = new Error('Not found'); + error.statusCode = 404; + throw error; + } + if (cronJobs[jobId]) { + cronJobs[jobId].stop(); + delete cronJobs[jobId]; + } + addCron(jobId, job[0], job[0].cron_expression, configData); + logger.info('Job updated successfully to database'); }; function createResponse(jobId, jobBody, runId) { diff --git a/src/reports/models/notifier.js b/src/reports/models/notifier.js index 95f35ac17..d5b0723f7 100644 --- a/src/reports/models/notifier.js +++ b/src/reports/models/notifier.js @@ -1,9 +1,7 @@ 'use strict'; const reportEmailSender = require('./reportEmailSender'), - reportWebhookSender = require('./reportWebhookSender'), jobsManager = require('../../jobs/models/jobManager'), - statsFromatter = require('../../webhooks/models/statsFormatter'), aggregateReportGenerator = require('./aggregateReportGenerator'), logger = require('../../common/logger'), constants = require('../utils/constants'), @@ -30,7 +28,7 @@ module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { switch (stats.phase_status) { case constants.SUBSCRIBER_FAILED_STAGE: { logger.info(metadata, stats.error, 'handling error message'); - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FAILED, { report, stats }); + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_FAILED, { report, stats }); break; } case constants.SUBSCRIBER_STARTED_STAGE: { @@ -67,7 +65,7 @@ async function handleStart(report, job) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_STARTED_STAGE)) { return; } - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_STARTED, { report }); + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_STARTED, report); } async function handleFirstIntermediate(report, job) { @@ -76,7 +74,7 @@ async function handleFirstIntermediate(report, job) { } // WHAT DO WE DO WITH BATCHES let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_IN_PROGRESS, { report, aggregatedReport }); + webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_IN_PROGRESS, { report, aggregatedReport }); } async function handleDone(report, job, reportBenchmark) { @@ -84,44 +82,25 @@ async function handleDone(report, job, reportBenchmark) { return; } let emails = await getEmailTargets(job); - const { benchmarkThreshold, benchmarkWebhook } = await getBenchmarkConfig(); - - if (emails.length === 0 && benchmarkWebhook.length === 0) { - return; - } + const { benchmarkThreshold } = await getBenchmarkConfig(); let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - if (emails.length > 0) { - reportEmailSender.sendAggregateReport(aggregatedReport, job, emails, reportBenchmark); + if (emails && emails.length > 0) { + await reportEmailSender.sendAggregateReport(aggregatedReport, job, emails, reportBenchmark); } - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FINISHED, { report, aggregatedReport, reportBenchmark }); - if (reportBenchmark.score && benchmarkThreshold) { const lastReports = await reportsManager.getReports(aggregatedReport.test_id); const lastScores = lastReports.slice(0, 3).filter(report => report.score).map(report => report.score.toFixed(1)); - let event = reportBenchmark.score < benchmarkThreshold ? WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED : WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED; - await webhooksManager.fireWebhookByEvent(job.id, event, { aggregatedReport, score: reportBenchmark.score, lastScores, icon: ':sad_1:' }); + const { event, icon } = reportBenchmark.score < benchmarkThreshold ? { event: WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, icon: ':sad_1:' } : { event: WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, icon: ':grin:' }; + await webhooksManager.fireWebhookByEvent(job, event, report, { aggregatedReport, score: reportBenchmark.score, lastScores, benchmarkThreshold }, { icon }); } + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_FINISHED, report, { aggregatedReport, score: reportBenchmark.score }, { icon: ':rocket:' }); } async function handleAbort(report, job) { - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_ABORTED, { report }); -} - -async function getWebhookTargets(job) { - let targets = []; - let defaultWebhookUrl = await configHandler.getConfigValue(configConstants.DEFAULT_WEBHOOK_URL); - - if (defaultWebhookUrl) { - targets.push(defaultWebhookUrl); - } - - if (job.webhooks) { - targets = targets.concat(job.webhooks); - } - return targets; + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_ABORTED, { report }); } async function getBenchmarkConfig() { diff --git a/src/reports/models/reportWebhookSender.js b/src/reports/models/reportWebhookSender.js deleted file mode 100644 index 2c91281e2..000000000 --- a/src/reports/models/reportWebhookSender.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -let request = require('request-promise-native'); - -let logger = require('../../common/logger'); - -module.exports.send = async (webhooks, message, options = {}) => { - if (!webhooks) { - return; - } - let finallOptions = { - body: - { - 'text': message, - 'icon_emoji': options.icon || ':muscle:', - 'username': 'reporter' - }, - json: true - }; - let promises = []; - webhooks.forEach(webhookUrl => { - promises.push(request.post(Object.assign({ url: webhookUrl }, finallOptions))); - }); - - try { - await Promise.all(promises); - } catch (error) { - logger.error(error, 'Failed to send webhooks'); - } -}; diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 1563f2633..e7d4c62c2 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -41,6 +41,7 @@ async function getWebhook(webhookId) { return parseWebhook(webhook); } +// TEST THIS FUNCTION async function getAllGlobalWebhooks() { const webhooksModel = client.model('webhook'); const webhooks = await webhooksModel.findAll({ include: ['events'], where: { global: true } }); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 9ff5e0edd..bdc2b2f80 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -44,8 +44,9 @@ async function updateWebhook(webhookId, webhook) { return databaseConnector.updateWebhook(webhookId, webhook); }; +// TEST THIS FUNCTION async function getAllGlobalWebhooks() { - return databaseConnector.getAllWebhooks(); + return databaseConnector.getAllGlobalWebhooks(); } async function fireSingleWebhook(webhook, payload) { @@ -56,27 +57,34 @@ async function fireSingleWebhook(webhook, payload) { url: webhook.url, body: payload }); - logger.info(`Webhook fired successfully, url=${webhook.url}`); + logger.info(`Webhook fired successfully, url = ${webhook.url}`); } catch (requestError) { - logger.error(`Webhook failed, url=${webhook.url}`); + logger.error(`Webhook failed, url = ${webhook.url}`); throw requestError; } - return webhookResponse; } -async function fireWebhooks(webhooks, payload) { - return webhooks.map(webhook => fireSingleWebhook(webhook, webhooksFormatter[webhook.format_type](payload))); +//format, eventType, jobId, testId, report, additionalInfo = {}, options = {} +function fireWebhooks(webhooks, eventType, jobId, testId, report, additionalInfo, options) { + return webhooks.map(webhook => fireSingleWebhook(webhook, webhooksFormatter(webhook.format_type, eventType, jobId, testId, report, additionalInfo, options))); } -async function fireWebhookByEvent(jobId, eventType, payload) { - const job = await getWebhook(jobId); +// FAILED: report, stats +// STARTED: report +// IN_PROGRESS: report, aggregatedReport +// report, aggregatedReport, reportBenchmark +// BENCHMARK_FAILED/PASSED: report, aggregatedReport, score, lastScores, icon +// ABORTED: report +async function fireWebhookByEvent(job, eventType, report, additionalInfo = {}, options = {}) { + const jobWebhooks = await Promise.all(job.webhooks.map(webhookId => getWebhook(webhookId))); const globalWebhooks = await getAllGlobalWebhooks(); - const webhooks = [...job.webhooks, ...globalWebhooks]; - const webhooksWithEventType = webhooks.filter(webhook => webhook.events.include(eventType)); + const webhooks = [...jobWebhooks, ...globalWebhooks]; + const webhooksWithEventType = webhooks.filter(webhook => webhook.events.includes(eventType)); if (webhooksWithEventType.length === 0) { return; } - await Promise.allSettled(fireWebhooks(webhooksWithEventType, payload)); + const webhooksPromises = fireWebhooks(webhooksWithEventType, eventType, job.id, job.test_id, report, additionalInfo, options); + await Promise.allSettled(webhooksPromises); } module.exports = { @@ -86,4 +94,4 @@ module.exports = { deleteWebhook, updateWebhook, fireWebhookByEvent -}; \ No newline at end of file +}; diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index 237f6972d..ee90d41aa 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -1,3 +1,5 @@ +const cloneDeep = require('lodash/cloneDeep'); + const { EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK, @@ -19,6 +21,18 @@ function unknownWebhookEventTypeError(badWebhookEventTypeValue) { return new Error(`Unrecognized webhook event: ${badWebhookEventTypeValue}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`); } +function getThresholdSlackMessage(state, { testName, benchmarkThreshold, lastScores, aggregatedReport, score }) { + let resultText = 'above'; + let icon = ':rocket:'; + if (state === WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED) { + resultText = 'below'; + icon = ':sad_1:'; + } + return `${icon} *Test ${testName} got a score of ${score.toFixed(1)}` + + ` this is ${resultText} the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + + `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`;; +} + function slackWebhookFormat(message, options) { return { text: message, @@ -27,89 +41,92 @@ function slackWebhookFormat(message, options) { }; } -function json(event, testId, jobId, report, options) { +function json(event, testId, jobId, report, additionalInfo, options) { let payload = { test_id: testId, job_id: jobId, event: event, - additional_details: {} - }; - let additionalDetails = {}; - switch (event) { - case WEBHOOK_EVENT_TYPE_STARTED: { - additionalDetails = { - test_name: , - environment: , - duration: , - arrival_rate: , - parallelism: , - ramp_to: rampTo, - }; - break; - } - case WEBHOOK_EVENT_TYPE_FINISHED: { - additionalDetails = { - test_name: , - grafana_report: , - aggregated_report: { - ...aggregatedReport.aggregate - }, - report_benchmark: { - ...reportBenchmark - } - }; - break; - } - case WEBHOOK_EVENT_TYPE_FAILED: { - additionalDetails = { - environment: , - stats: { - ...stats.data - } - }; - break; - } - case WEBHOOK_EVENT_TYPE_ABORTED: { - additionalDetails = { - testName, - grafanaReport: - }; - break; - } - case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { - additionalDetails = { - aggregated_test_name: , - benchmark_threshold: , - score: , - last_three_scores: , - aggregated_report: - }; - break; - } - case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { - break; - } - case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { - additionalDetails = { + additional_details: { + ...cloneDeep({ report, ...additionalInfo }), - }; - break; } - case WEBHOOK_EVENT_TYPE_API_FAILURE: { - additionalDetails = { + }; + // let additionalDetails = {}; + // switch (event) { + // case WEBHOOK_EVENT_TYPE_STARTED: { + // additionalDetails = { + // test_name: , + // environment: , + // duration: , + // arrival_rate: , + // parallelism: , + // ramp_to: rampTo, + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_FINISHED: { + // additionalDetails = { + // test_name: , + // grafana_report: , + // aggregated_report: { + // ...aggregatedReport.aggregate + // }, + // report_benchmark: { + // ...reportBenchmark + // } + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_FAILED: { + // additionalDetails = { + // environment: , + // stats: { + // ...stats.data + // } + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_ABORTED: { + // additionalDetails = { + // testName, + // grafanaReport: + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { + // additionalDetails = { + // aggregated_test_name: , + // benchmark_threshold: , + // score: , + // last_three_scores: , + // aggregated_report: + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { + // break; + // } + // case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + // additionalDetails = { - }; - break; - } - default: { - throw unknownWebhookEventTypeError(); - } - } - payload.additional_details = additionalDetails; + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_API_FAILURE: { + // additionalDetails = { + + // }; + // break; + // } + // default: { + // throw unknownWebhookEventTypeError(); + // } + // } + // payload.additional_details = additionalDetails; return payload; } -function slack(event, testId, jobId, report, options) { +function slack(event, testId, jobId, report, additionalInfo, options) { let message = null; const { environment, @@ -120,20 +137,16 @@ function slack(event, testId, jobId, report, options) { test_name: testName, grafana_report: grafanaReport } = report; - const { aggregatedReport, reportBenchmark } = options; + const { score, aggregatedReport, reportBenchmark, benchmarkThreshold, lastScores, stats } = additionalInfo; switch (event) { case WEBHOOK_EVENT_TYPE_STARTED: { - let rampToMessage = rampTo ? `, ramp to: ${rampTo} scenarios per second` : ''; + let rampToMessage = rampTo ? `ramp to: ${rampTo} scenarios per second` : ''; message = `🤓 *Test ${testName} with id: ${testId} has started*.\n - *test configuration:* environment: ${environment} duration: ${duration} seconds, arrival rate: ${arrivalRate} scenarios per second, number of runners: ${parallelism}${rampToMessage}`; + *test configuration:* environment: ${environment} duration: ${duration} seconds, arrival rate: ${arrivalRate} scenarios per second, number of runners: ${parallelism}, ${rampToMessage}`; break; } case WEBHOOK_EVENT_TYPE_FINISHED: { - message = `😎 *Test ${testName} with id: ${testId} is finished.*\n - ${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; - if (grafanaReport) { - message += `<${grafanaReport}|View final grafana dashboard report>`; - } + message = `😎 *Test ${testName} with id: ${testId} is finished.*\n ${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; break; } case WEBHOOK_EVENT_TYPE_FAILED: { @@ -145,43 +158,41 @@ function slack(event, testId, jobId, report, options) { } case WEBHOOK_EVENT_TYPE_ABORTED: { message = `😢 *Test ${testName} with id: ${testId} was aborted.*\n`; - if (grafanaReport) { - message += `<${grafanaReport}|View final grafana dashboard report>`; - } break; } - case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { - message = `:sad_1: *Test ${aggregatedtestName} got a score of ${score.toFixed(1)}` + - ` this is below the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + - `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; + case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: + case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + message = getThresholdSlackMessage(event, { testName, lastScores, benchmarkThreshold, score }); break; } case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { - break; - } - case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + message = `:hammer_and_wrench: *Test ${testName} with id: ${testId} is in progress!*`; break; } case WEBHOOK_EVENT_TYPE_API_FAILURE: { + message = `::boom:: *Test ${testName} with id: ${testId} has encountered an API failure!*`; break; } default: { throw unknownWebhookEventTypeError(); } } - return slackWebhookFormat(message); + if (grafanaReport) { + message += `<${grafanaReport} | View final grafana dashboard report>`; + } + return slackWebhookFormat(message, options); } -module.exports = function(format, eventType, jobId, testId, report, options={}) { - switch(format) { +module.exports = function(format, eventType, jobId, testId, report, additionalInfo = {}, options = {}) { + switch (format) { case EVENT_FORMAT_TYPE_SLACK: { - return slack(eventType, testId, jobId, report, options); + return slack(eventType, testId, jobId, report, additionalInfo, options); } case EVENT_FORMAT_TYPE_JSON: { - return json(eventType, testId, jobId, report, options); + return json(eventType, testId, jobId, report, additionalInfo, options); } default: { - throw new Error(`Unrecognized webhook format: ${format}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`) + throw new Error(`Unrecognized webhook format: ${format}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`); } } -} \ No newline at end of file +}; diff --git a/tests/testExamples/Basic_test.json b/tests/testExamples/Basic_test.json index e57b432f8..6ab4f641d 100644 --- a/tests/testExamples/Basic_test.json +++ b/tests/testExamples/Basic_test.json @@ -32,4 +32,4 @@ }] }] } - } \ No newline at end of file +} diff --git a/tests/unit-tests/jobs/models/jobManager-test.js b/tests/unit-tests/jobs/models/jobManager-test.js index c59016ed3..73bd46a79 100644 --- a/tests/unit-tests/jobs/models/jobManager-test.js +++ b/tests/unit-tests/jobs/models/jobManager-test.js @@ -10,7 +10,6 @@ const should = require('should'), jobTemplate = require('../../../../src/jobs/models/kubernetes/jobTemplate'), config = require('../../../../src/common/consts').CONFIG; - let manager; const TEST_ID = '5a9eee73-cf56-47aa-ac77-fad59e961aaa'; @@ -109,6 +108,8 @@ const jobBodyWithCustomEnvVars = { max_virtual_users: 100 }; +// TODO: this tests suite runs over cassandra, cassandra should be dropped + describe('Manager tests', function () { let sandbox; let cassandraInsertStub; @@ -160,7 +161,7 @@ describe('Manager tests', function () { getConfigValueStub.withArgs(config.JOB_PLATFORM).returns('KUBERNETES'); }); - beforeEach(async () => { + beforeEach(async () => { await manager.init(); sandbox.resetHistory(); }); diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index a70371998..c737f6c08 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -2,6 +2,8 @@ let sinon = require('sinon'); let rewire = require('rewire'); let should = require('should'); +const cloneDeep = require('lodash/cloneDeep'); + let databaseConfig = require('../../../../src/config/databaseConfig'); let sequelizeConnector = rewire('../../../../src/jobs/models/database/sequelize/sequelizeConnector'); @@ -18,6 +20,8 @@ describe('Sequelize client tests', function () { let sequelizeUpdateStub; let sequelizeGetStub; let sequelizeDestroyStub; + let sequelizeTransactionStub; + const transaction = {}; before(() => { sandbox = sinon.sandbox.create(); @@ -39,7 +43,7 @@ describe('Sequelize client tests', function () { sequelizeCloseStub = sandbox.stub(); sequelizeStub = sandbox.stub(); sequelizeCloseStub = sandbox.stub(); - sequelizeStub = sandbox.stub(); + sequelizeTransactionStub = sandbox.stub(); sequelizeDefineStub.returns({ hasMany: () => { @@ -56,14 +60,16 @@ describe('Sequelize client tests', function () { create: sequelizeCreateStub, update: sequelizeUpdateStub, findAll: sequelizeGetStub, - destroy: sequelizeDestroyStub + destroy: sequelizeDestroyStub, + findByPk: sequelizeGetStub }); sequelizeStub.returns({ authenticate: sequelizeAuthenticateStub, model: sequelizeModelStub, define: sequelizeDefineStub, - close: sequelizeCloseStub + close: sequelizeCloseStub, + transaction: sequelizeTransactionStub }); sequelizeStub.DataTypes = {}; sequelizeConnector.__set__('Sequelize', sequelizeStub); @@ -73,6 +79,7 @@ describe('Sequelize client tests', function () { }); afterEach(() => { sandbox.resetHistory(); + sandbox.resetBehavior(); }); after(() => { @@ -93,7 +100,7 @@ describe('Sequelize client tests', function () { let id = uuid.v4(); let testId = uuid.v4(); - await sequelizeConnector.insertJob(id, { + const job = { test_id: testId, arrival_rate: 1, duration: 1, @@ -101,44 +108,45 @@ describe('Sequelize client tests', function () { emails: ['hello@zooz.com', 'hello@payu.com'], environment: 'test', ramp_to: '1', - webhooks: ['http://zooz.com', 'http://payu.com'], + webhooks: ['UUIDSTUB'], parallelism: 4, max_virtual_users: 100, notes: 'some nice notes', proxy_url: 'http://proxy.com', debug: '*', enabled: true - }); + }; + const createdJob = { + dataValues: { + ...job, + id, + webhooks: ['UUIDSTUB'], + emails: [{ + 'id': 'UUIDSTUB', + 'address': 'hello@zooz.com' + }, { + 'id': 'UUIDSTUB', + 'address': 'hello@payu.com' + }] + }, + setWebhooks: sandbox.stub() + }; - should(sequelizeCreateStub.args[0][0]).eql({ - 'id': id, - 'test_id': testId, - 'arrival_rate': 1, - 'cron_expression': '* * * *', - 'duration': 1, - 'environment': 'test', - 'ramp_to': '1', - 'parallelism': 4, - 'notes': 'some nice notes', - 'max_virtual_users': 100, - 'proxy_url': 'http://proxy.com', - 'debug': '*', - 'enabled': true, - 'webhooks': [{ - 'id': 'UUIDSTUB', - 'url': 'http://zooz.com' - }, { - 'id': 'UUIDSTUB', - 'url': 'http://payu.com' - }], - 'emails': [{ - 'id': 'UUIDSTUB', - 'address': 'hello@zooz.com' - }, { - 'id': 'UUIDSTUB', - 'address': 'hello@payu.com' - }] - }); + createdJob.setWebhooks.resolves(); + sequelizeCreateStub.resolves(createdJob); + sequelizeTransactionStub.resolves(); + + await sequelizeConnector.insertJob(id, job); + await sequelizeTransactionStub.yields(transaction); + + const jobParams = cloneDeep(job); + jobParams.emails = createdJob.dataValues.emails; + jobParams.id = createdJob.dataValues.id; + delete jobParams.webhooks; + + should(sequelizeCreateStub.args[0][0]).deepEqual(jobParams); + should(createdJob.setWebhooks.calledOnce).eql(true); + should(createdJob.setWebhooks.args[0][0]).deepEqual(job.webhooks); }); it('should succeed insert without webhooks and emails', async () => { @@ -147,41 +155,47 @@ describe('Sequelize client tests', function () { let id = uuid.v4(); let testId = uuid.v4(); - await sequelizeConnector.insertJob(id, { + const job = { test_id: testId, arrival_rate: 1, duration: 1, cron_expression: '* * * *', environment: 'test', ramp_to: '1', + enabled: true, parallelism: 4, max_virtual_users: 100, notes: 'some notes', proxy_url: 'http://proxy.com', debug: '*' - }); + }; - should(sequelizeCreateStub.args[0][0]).eql({ - 'id': id, - 'test_id': testId, - 'arrival_rate': 1, - 'cron_expression': '* * * *', - 'duration': 1, - 'environment': 'test', - 'ramp_to': '1', - 'webhooks': undefined, - 'emails': undefined, - 'notes': 'some notes', - 'parallelism': 4, - 'max_virtual_users': 100, - 'proxy_url': 'http://proxy.com', - 'debug': '*', - 'enabled': undefined - }); + const createdJob = { + dataValues: { + ...job, + id + }, + setWebhooks: sandbox.stub() + }; + + sequelizeCreateStub.resolves(createdJob); + createdJob.setWebhooks.resolves(); + sequelizeTransactionStub.resolves(); + + await sequelizeConnector.insertJob(id, job); + await sequelizeTransactionStub.yield(transaction); + + const jobParams = cloneDeep(job); + jobParams.emails = createdJob.dataValues.emails; + jobParams.id = createdJob.dataValues.id; + + should(sequelizeCreateStub.args[0][0]).eql(jobParams); + should(createdJob.setWebhooks.calledOnce).eql(true); + should(createdJob.setWebhooks.args[0][0]).deepEqual([]); }); it('should log error for failing inserting new test', async () => { - sequelizeCreateStub.rejects(new Error('Sequelize Error')); + sequelizeTransactionStub.rejects(new Error('Sequelize Error')); await sequelizeConnector.init(sequelizeStub()); @@ -207,57 +221,76 @@ describe('Sequelize client tests', function () { let sequelizeResponse = [{ dataValues: { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'environment': 'test', - 'cron_expression': null, - 'arrival_rate': 100, - 'duration': 1700, - 'ramp_to': null, - 'webhooks': [{ - dataValues: { - 'id': '8138e406-0d5f-4caf-a143-a758b9545b75', - 'url': 'https://hooks.slack.com/services/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', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' - } - }, { - dataValues: { - 'id': 'e38b985f-efec-4315-93bf-6f04eb2b7438', - 'url': 'http://www.one.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + environment: 'test', + cron_expression: null, + arrival_rate: 100, + duration: 1700, + ramp_to: null, + webhooks: [ + { + dataValues: { + id: '8138e406-0d5f-4caf-a143-a758b9545b75', + name: 'avi3', + url: 'https://1f19d804b781f93bfe92e6dac1b96572.m.pipedream.net', + global: false, + format_type: 'json', + created_at: '2020-08-10T20:37:49.313Z', + updated_at: '2020-08-10T20:37:49.313Z', + webhook_job_mapping: { + created_at: '2020-08-11T16:12:35.634Z', + updated_at: '2020-08-11T16:12:35.634Z', + webhook_id: '04e844d6-c0c4-46e9-a752-ba66035e047c', + job_id: 'c941ea0f-7b3d-4b05-a01a-7961c7735e04' + } + } + }, + { + dataValues: { + id: 'e38b985f-efec-4315-93bf-6f04eb2b7438', + name: 'avi', + url: 'https://1f19d804b781f93bfe92e6dac1b96572.m.pipedream.net', + global: false, + format_type: 'json', + created_at: '2020-08-10T20:10:26.573Z', + updated_at: '2020-08-10T20:10:26.573Z', + webhook_job_mapping: { + created_at: '2020-08-11T16:12:35.634Z', + updated_at: '2020-08-11T16:12:35.634Z', + webhook_id: '5a862394-6e13-45e0-a478-7cd069a2d438', + job_id: 'c941ea0f-7b3d-4b05-a01a-7961c7735e04' + } + } } - }], - 'emails': [{ + ], + emails: [{ dataValues: { - 'id': '8bd6a285-9d9f-4e07-a8e3-387f5936c347', - 'address': 'eli.nudler@zooz.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: '8bd6a285-9d9f-4e07-a8e3-387f5936c347', + address: 'eli.nudler@zooz.com', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }, { dataValues: { - 'id': 'edc77399-ea72-4b0a-97da-c6169e59bb52', - 'address': 'xyz@om.ds', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'edc77399-ea72-4b0a-97da-c6169e59bb52', + address: 'xyz@om.ds', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }] } }, { dataValues: { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59d', - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', - 'environment': 'test', - 'cron_expression': null, - 'arrival_rate': 200, - 'duration': 2000, - 'ramp_to': null + id: 'd6b0f076-2efb-48e1-82d2-82250818f59d', + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', + environment: 'test', + cron_expression: null, + arrival_rate: 200, + duration: 2000, + ramp_to: null } }]; @@ -266,32 +299,32 @@ describe('Sequelize client tests', function () { should(jobs.length).eql(2); should(jobs[0]).eql({ - 'arrival_rate': 100, - 'cron_expression': null, - 'duration': 1700, - 'emails': [ + arrival_rate: 100, + cron_expression: null, + duration: 1700, + emails: [ 'eli.nudler@zooz.com', 'xyz@om.ds' ], - 'environment': 'test', - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'ramp_to': null, - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'webhooks': [ - 'https://hooks.slack.com/services/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', - 'http://www.one.com' + environment: 'test', + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + ramp_to: null, + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + webhooks: [ + '8138e406-0d5f-4caf-a143-a758b9545b75', + 'e38b985f-efec-4315-93bf-6f04eb2b7438' ] }); should(jobs[1]).eql({ - 'arrival_rate': 200, - 'cron_expression': null, - 'duration': 2000, - 'environment': 'test', - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59d', - 'ramp_to': null, - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', - 'webhooks': undefined, - 'emails': undefined + arrival_rate: 200, + cron_expression: null, + duration: 2000, + environment: 'test', + id: 'd6b0f076-2efb-48e1-82d2-82250818f59d', + ramp_to: null, + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', + webhooks: undefined, + emails: undefined }); }); @@ -326,45 +359,45 @@ describe('Sequelize client tests', function () { let sequelizeResponse = [{ dataValues: { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'environment': 'test', - 'cron_expression': null, - 'arrival_rate': 100, - 'duration': 1700, - 'ramp_to': null, - 'webhooks': [{ + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + environment: 'test', + cron_expression: null, + arrival_rate: 100, + duration: 1700, + ramp_to: null, + webhooks: [{ dataValues: { - 'id': '8138e406-0d5f-4caf-a143-a758b9545b75', - 'url': 'https://hooks.slack.com/services/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', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: '8138e406-0d5f-4caf-a143-a758b9545b75', + url: 'https://hooks.slack.com/services/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', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }, { dataValues: { - 'id': 'e38b985f-efec-4315-93bf-6f04eb2b7438', - 'url': 'http://www.one.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'e38b985f-efec-4315-93bf-6f04eb2b7438', + url: 'http://www.one.com', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }], - 'emails': [{ + emails: [{ dataValues: { - 'id': '8bd6a285-9d9f-4e07-a8e3-387f5936c347', - 'address': 'eli.nudler@zooz.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: '8bd6a285-9d9f-4e07-a8e3-387f5936c347', + address: 'eli.nudler@zooz.com', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }, { dataValues: { - 'id': 'edc77399-ea72-4b0a-97da-c6169e59bb52', - 'address': 'xyz@om.ds', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'edc77399-ea72-4b0a-97da-c6169e59bb52', + address: 'xyz@om.ds', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }] } @@ -374,36 +407,36 @@ describe('Sequelize client tests', function () { let jobs = await sequelizeConnector.getJob('d6b0f076-2efb-48e1-82d2-82250818f59c'); should(jobs).eql([{ - 'arrival_rate': 100, - 'cron_expression': null, - 'duration': 1700, - 'emails': [ + arrival_rate: 100, + cron_expression: null, + duration: 1700, + emails: [ 'eli.nudler@zooz.com', 'xyz@om.ds' ], - 'environment': 'test', - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'ramp_to': null, - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'webhooks': [ - 'https://hooks.slack.com/services/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', - 'http://www.one.com' + environment: 'test', + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + ramp_to: null, + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + webhooks: [ + '8138e406-0d5f-4caf-a143-a758b9545b75', + 'e38b985f-efec-4315-93bf-6f04eb2b7438' ] }]); should(sequelizeGetStub.args[0][0]).eql({ - 'attributes': { - 'exclude': [ + attributes: { + exclude: [ 'updated_at', 'created_at' ] }, - 'include': [ + include: [ {}, 'webhooks' ], - 'where': { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + where: { + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }); }); @@ -464,44 +497,45 @@ describe('Sequelize client tests', function () { let id = uuid.v4(); let testId = uuid.v4(); + const webhookId = uuid.v4(); - await sequelizeConnector.updateJob(id, { - test_id: testId, - arrival_rate: 1, - duration: 1, - cron_expression: '* * * *', - environment: 'test', - ramp_to: '1', - max_virtual_users: 500, - parallelism: 3, - proxy_url: 'http://proxy.com', - debug: '*', - enabled: false - }); - - should(sequelizeUpdateStub.args[0][0]).eql({ - 'test_id': testId, - 'arrival_rate': 1, - 'cron_expression': '* * * *', - 'duration': 1, - 'environment': 'test', - 'ramp_to': '1', - 'max_virtual_users': 500, - 'parallelism': 3, - 'proxy_url': 'http://proxy.com', - 'debug': '*', - 'enabled': false - }); - - should(sequelizeUpdateStub.args[0][1]).eql({ - 'where': { - 'id': id - } - }); + const sequelizeJobResponse = { + dataValues: { + test_id: testId, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * *', + environment: 'test', + ramp_to: '1', + max_virtual_users: 500, + parallelism: 3, + proxy_url: 'http://proxy.com', + debug: '*', + enabled: false + }, + setWebhooks: sandbox.stub() + }; + const updatedJobInfo = { + ...sequelizeJobResponse.dataValues, + cron_expression: '5 4 * *', + proxy_url: 'http://predator.dev', + webhooks: [ webhookId ] + }; + sequelizeGetStub.resolves(sequelizeJobResponse); + sequelizeTransactionStub.resolves(); + sequelizeJobResponse.setWebhooks.resolves(); + + await sequelizeConnector.updateJob(id, updatedJobInfo); + await sequelizeTransactionStub.yield(transaction); + + const { webhooks, ...updatedJobInfoWithoutWebhooks } = updatedJobInfo; + + should(sequelizeUpdateStub.args[0][0]).eql(updatedJobInfoWithoutWebhooks); + should(sequelizeUpdateStub.args[0][1]).eql({ where: { id }, transaction }); }); it('should log error for failing updating test', async () => { - sequelizeUpdateStub.rejects(new Error('Sequelize Error')); + sequelizeTransactionStub.rejects(new Error('Sequelize Error')); await sequelizeConnector.init(sequelizeStub()); @@ -520,4 +554,4 @@ describe('Sequelize client tests', function () { } }); }); -}); \ No newline at end of file +}); From 63998bbda45d4055b83205b0ba8e34f82f5a1371 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 24 May 2020 10:34:21 +0300 Subject: [PATCH 12/38] docs(webhooks): create webhooks API spec (#308) * docs(webhooks): create webhooks API spec --- docs/openapi3.yaml | 171 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 1d912536b..6fbec0258 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -43,6 +43,9 @@ tags: - name: Benchmarks description: | By creating a benchmark for a specific test, each subsequent test run for that test will be given a score from 0-100 summarizing the test run in one simple to analyze numerical value. + - name: Webhooks + description: | + This resource allows you to configure webhooks. x-tagGroups: - name: Reference tags: @@ -54,6 +57,7 @@ x-tagGroups: - Configuration - Files - Benchmarks + - Webhooks paths: #DSL Definitions @@ -1382,6 +1386,124 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + # Webhooks + /v1/webhooks: + get: + operationId: retrieve-webhooks + tags: + - Webhooks + summary: Retrieve webhooks + description: Retrieve all webhooks. + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + post: + operationId: create-a-webhook + tags: + - Webhooks + summary: Create a Webhook + description: Create a new Webhook. + responses: + '201': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + description: The webhook to add + required: true + /v1/webhooks/{webhook_id}: + get: + operationId: retrieve-a-webhook + tags: + - Webhooks + summary: Retrieve a webhook by id + description: Retrieve a webhook by id. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + put: + operationId: update-a-webhook + tags: + - Webhooks + summary: Update a webhook + description: Update a webhook. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' # Files /v1/files: @@ -1944,7 +2066,8 @@ components: The event body will include detailed information about the test, such as the number of scenarios that were executed and the number of requests that were invoked. items: type: string - description: The url of where to send the webhook with the report information + format: uuid + description: The id of the webhook arrival_rate: type: number minimum: 1 @@ -2381,3 +2504,49 @@ components: filename: type: string description: the name of the file + type: number + description: benchmark percentage weight + webhook: + type: object + required: + - name + - webhook_url + - events + - format_type + properties: + id: + description: Unique webhook identifier + type: string + format: uuid + readOnly: true + name: + type: string + description: Webhook name + webhook_url: + type: string + description: Webhook url to post events + events: + description: list of events which will trigger the webhook + type: array + items: + $ref: '#/components/schemas/webhooks_types' + format_type: + $ref: '#/components/schemas/webhook_format_types' + global: + type: boolean + description: indicates whether the webhook should be applied globally(over all jobs) + webhooks_types: + type: string + enum: + - started + - api_failure + - aborted + - failed + - finished + - benchmark_passed + - benchmark_failed + webhook_format_types: + type: string + enum: + - slack + - json From f8534a1872d0c97ffadd7813c685ce748d9f9a3e Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 15 Jun 2020 10:23:30 +0300 Subject: [PATCH 13/38] implement GET /webhooks (#318) --- .eslintrc.json | 4 +- docs/openapi3.yaml | 9 +- package-lock.json | 2 +- src/app.js | 2 + src/common/consts.js | 32 +++++++ .../migrations/04_webhooks.js | 62 ++++++++++++ src/database/sequlize-handler/sequlize.js | 3 + .../database/sequelize/sequelizeConnector.js | 29 +++--- .../controllers/webhooksController.js | 12 +++ .../database/cassandra/cassandraConnector.js | 16 ++++ .../models/database/databaseConnector.js | 21 ++++ .../database/sequelize/sequelizeConnector.js | 69 ++++++++++++++ src/webhooks/models/webhookManager.js | 8 ++ src/webhooks/routes/webhooksRouter.js | 11 +++ .../jobs/sequelize/sequelizeConnector-test.js | 2 +- .../sequelize/sequelizeConnector-test.js | 95 +++++++++++++++++++ 16 files changed, 354 insertions(+), 23 deletions(-) create mode 100644 src/database/sequlize-handler/migrations/04_webhooks.js create mode 100644 src/webhooks/controllers/webhooksController.js create mode 100644 src/webhooks/models/database/cassandra/cassandraConnector.js create mode 100644 src/webhooks/models/database/databaseConnector.js create mode 100644 src/webhooks/models/database/sequelize/sequelizeConnector.js create mode 100644 src/webhooks/models/webhookManager.js create mode 100644 src/webhooks/routes/webhooksRouter.js create mode 100644 tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js diff --git a/.eslintrc.json b/.eslintrc.json index 3d06b8857..6f95b6671 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,8 @@ "plugins": [], "env": { "node": true, - "mocha": true + "mocha": true, + "es6": true }, "parserOptions": { "ecmaVersion": 8, @@ -20,4 +21,3 @@ "camelcase": ["warn"] } } - \ No newline at end of file diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 6fbec0258..f71549fe7 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -2062,8 +2062,9 @@ components: webhooks: type: array description: | - A URL to which an event will be sent when the test execution is completed. - The event body will include detailed information about the test, such as the number of scenarios that were executed and the number of requests that were invoked. + An array of webhooks ids, events will be fired to the coresponding webhooks according to their events configuration. + The event body will include detailed information about the test, such as the number of scenarios that were executed + and the number of requests that were invoked. items: type: string format: uuid @@ -2510,7 +2511,7 @@ components: type: object required: - name - - webhook_url + - url - events - format_type properties: @@ -2522,7 +2523,7 @@ components: name: type: string description: Webhook name - webhook_url: + url: type: string description: Webhook url to post events events: diff --git a/package-lock.json b/package-lock.json index 86b832804..4d72c0ab0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11067,7 +11067,7 @@ }, "streamsearch": { "version": "0.1.2", - "resolved": "http://npm.zooz.co:8083/streamsearch/-/streamsearch-0.1.2.tgz", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, "string-width": { diff --git a/src/app.js b/src/app.js index 1253e1e95..4414bfacc 100644 --- a/src/app.js +++ b/src/app.js @@ -10,6 +10,7 @@ let dslRouter = require('./tests/routes/dslRoute.js'); let testsRouter = require('./tests/routes/testsRoute.js'); let processorsRouter = require('./processors/routes/processorsRoute.js'); let filesRouter = require('./files/routes/filesRoute.js'); +let webhooksRouter = require('./webhooks/routes/webhooksRouter'); let swaggerValidator = require('express-ajv-swagger-validation'); let audit = require('express-requests-logger'); @@ -66,6 +67,7 @@ module.exports = async () => { app.use('/v1/tests', testsRouter); app.use('/v1/processors', processorsRouter); app.use('/v1/files', filesRouter); + app.use('/v1/webhooks', webhooksRouter); app.use('/', function (req, res, next) { res.redirect('/ui'); diff --git a/src/common/consts.js b/src/common/consts.js index 8db47fce3..ebf0b2de2 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -1,7 +1,39 @@ +const EVENT_FORMAT_TYPE_SLACK = 'slack'; +const EVENT_FORMAT_TYPE_JSON = 'json'; +const WEBHOOK_EVENT_TYPE_STARTED = 'started'; +const WEBHOOK_EVENT_TYPE_FINISHED = 'finished'; +const WEBHOOK_EVENT_TYPE_API_FAILURE = 'api_failure'; +const WEBHOOK_EVENT_TYPE_ABORTED = 'aborted'; +const WEBHOOK_EVENT_TYPE_FAILED = 'failed'; +const WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED = 'benchmark_passed'; +const WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED = 'benchmark_failed'; + module.exports = { TEST_TYPE_BASIC: 'basic', TEST_TYPE_DSL: 'dsl', PROCESSOR_FUNCTIONS_KEYS: ['beforeScenario', 'afterScenario', 'beforeRequest', 'afterResponse'], + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + EVENT_FORMAT_TYPE_SLACK, + EVENT_FORMAT_TYPE_JSON, + EVENT_FORMAT_TYPES: [ + EVENT_FORMAT_TYPE_SLACK, + EVENT_FORMAT_TYPE_JSON + ], + WEBHOOK_EVENT_TYPES: [ + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED + ], ERROR_MESSAGES: { NOT_FOUND: 'Not found', DSL_DEF_ALREADY_EXIST: 'Definition already exists', diff --git a/src/database/sequlize-handler/migrations/04_webhooks.js b/src/database/sequlize-handler/migrations/04_webhooks.js new file mode 100644 index 000000000..88a3a4a8a --- /dev/null +++ b/src/database/sequlize-handler/migrations/04_webhooks.js @@ -0,0 +1,62 @@ +const Sequelize = require('sequelize'); +const uuid = require('uuid'); +const { WEBHOOK_EVENT_TYPES } = require('../../../common/consts'); + +const tableName = 'webhooks'; +const webhookEventsTableName = 'webhook_events'; +const webhookEventMappingTableName = 'webhook_events'; +const webhookJobsMappingTableName = 'webhook_job_mapping'; +const columns = [ + { + name: 'name', + dt: Sequelize.DataTypes.TEXT('medium') + }, + { + name: 'global', + dt: Sequelize.DataTypes.BOOLEAN + } +]; + +async function takeActionOnColumn(describedTable, newColumnName, existAsyncAction, notExistAsyncAction) { + if (describedTable[newColumnName]) { + return existAsyncAction(); + } + return notExistAsyncAction(); +} + +function createEnumRow(name) { + return { + name, + id: uuid(), + created_at: new Date(), + updated_at: new Date() + }; +} + +module.exports.up = async (query, DataTypes) => { + let describedWebhooks = await query.describeTable(tableName); + const webhooksEventTypes = WEBHOOK_EVENT_TYPES.map(createEnumRow); + const promises = [ + ...columns.map(({ name, dt }) => + takeActionOnColumn( + describedWebhooks, + name, + () => null, + () => query.addColumn(tableName, name, dt) + ) + ) + ]; + await Promise.all(promises); + await query.bulkUpdate(tableName, { name: 'Webhook', global: false }); + await query.bulkInsert(webhookEventsTableName, webhooksEventTypes); +}; + +module.exports.down = async (query, DataTypes) => { + const promises = [ + ...columns.map(({ name }) => query.removeColumn(tableName, name)), + query.dropTable(webhookEventMappingTableName), + query.dropTable(webhookEventsTableName), + query.dropTable(webhookJobsMappingTableName) + ]; + await Promise.all(promises); +}; diff --git a/src/database/sequlize-handler/sequlize.js b/src/database/sequlize-handler/sequlize.js index 1af946d5c..ca05fa48d 100644 --- a/src/database/sequlize-handler/sequlize.js +++ b/src/database/sequlize-handler/sequlize.js @@ -10,11 +10,13 @@ const processorsSequlizeConnector = require('../../processors/models/database/se const fileSequlizeConnector = require('../../files/models/database/sequelize/sequelizeConnector'); const logger = require('../../../src/common/logger'); const databaseConfig = require('../../config/databaseConfig'); +const webhooksSequlizeConnector = require('../../webhooks/models/database/sequelize/sequelizeConnector'); const Sequelize = require('sequelize'); let sequlizeClient; module.exports.init = async () => { sequlizeClient = await createClient(); + await webhooksSequlizeConnector.init(sequlizeClient); await schedulerSequlizeConnector.init(sequlizeClient); await reportsSequlizeConnector.init(sequlizeClient); await testsSequlizeConnector.init(sequlizeClient); @@ -22,6 +24,7 @@ module.exports.init = async () => { await processorsSequlizeConnector.init(sequlizeClient); await fileSequlizeConnector.init(sequlizeClient); await runSequlizeMigrations(); + await sequlizeClient.sync(); }; module.exports.ping = async () => { diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index 6f4cab06f..c56c18c55 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -34,7 +34,7 @@ async function insertJob(jobId, jobInfo) { proxy_url: jobInfo.proxy_url, enabled: jobInfo.enabled, debug: jobInfo.debug, - webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { + webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { // still missing data attributes(name, global, format_type) return { id: uuid(), url: webhookUrl }; }) : undefined, emails: jobInfo.emails ? jobInfo.emails.map(emailAddress => { @@ -57,7 +57,7 @@ async function getJobsAndParse(jobId) { let options = { attributes: { exclude: ['updated_at', 'created_at'] }, - include: [job.webhook, job.email] + include: [job.email, 'webhooks'] }; if (jobId) { @@ -119,16 +119,6 @@ async function deleteJob(jobId) { } async function initSchemas() { - const webhook = client.define('webhook', { - id: { - type: Sequelize.DataTypes.UUID, - primaryKey: true - }, - url: { - type: Sequelize.DataTypes.STRING - } - }); - const email = client.define('email', { id: { type: Sequelize.DataTypes.UUID, @@ -181,10 +171,19 @@ async function initSchemas() { type: Sequelize.DataTypes.BOOLEAN } }); - - job.webhook = job.hasMany(webhook); job.email = job.hasMany(email); await job.sync(); - await webhook.sync(); await email.sync(); + + const webhooks = client.model('webhook'); + webhooks.belongsToMany(job, { + through: 'webhook_job_mapping', + as: 'jobs', + foreignKey: 'webhook_id' + }); + job.belongsToMany(webhooks, { + through: 'webhook_job_mapping', + as: 'webhooks', + foreignKey: 'job_id' + }); } \ No newline at end of file diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js new file mode 100644 index 000000000..aa068ad11 --- /dev/null +++ b/src/webhooks/controllers/webhooksController.js @@ -0,0 +1,12 @@ +'use strict'; +let webhookManager = require('../models/webhookManager'); + +module.exports.getAllWebhooks = async function (req, res, next) { + let webhooks; + try { + webhooks = await webhookManager.getAllWebhooks(); + return res.status(200).json(webhooks); + } catch (err) { + return next(err); + } +}; diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js new file mode 100644 index 000000000..b22b54435 --- /dev/null +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -0,0 +1,16 @@ +let logger = require('../../../../common/logger'); + +module.exports = { + init, + getAllWebhooks +}; + +async function init() { + const errorMessage = 'Webhooks feature is not implemented over Cassandra'; + logger.fatal(errorMessage); + throw new Error(errorMessage); +} + +async function getAllWebhooks() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js new file mode 100644 index 000000000..de1c28e3f --- /dev/null +++ b/src/webhooks/models/database/databaseConnector.js @@ -0,0 +1,21 @@ +let databaseConfig = require('../../../config/databaseConfig'); +let cassandraConnector = require('./cassandra/cassandraConnector'); +let sequelizeConnector = require('./sequelize/sequelizeConnector'); +let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +module.exports = { + init, + getAllWebhooks, + closeConnection +}; + +async function init() { + return databaseConnector.init(); +} + +function closeConnection() { + return databaseConnector.closeConnection(); +} + +async function getAllWebhooks(from, limit, exclude) { + return databaseConnector.getAllWebhooks(from, limit, exclude); +} \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js new file mode 100644 index 000000000..0045016b4 --- /dev/null +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -0,0 +1,69 @@ +'use strict'; + +const Sequelize = require('sequelize'); +let client; + +module.exports = { + init, + getAllWebhooks +}; + +async function init(sequelizeClient) { + client = sequelizeClient; + await initSchemas(); +} + +async function getAllWebhooks() { + const webhooksModel = client.model('webhook'); + return webhooksModel.findAll({ include: ['events'] }); +} + +async function initSchemas() { + const webhooksSchema = client.define('webhook', { + id: { + type: Sequelize.DataTypes.UUID, + primaryKey: true + }, + name: { + type: Sequelize.DataTypes.TEXT('medium') + }, + url: { + type: Sequelize.DataTypes.STRING + }, + global: { + type: Sequelize.DataTypes.BOOLEAN + }, + format_type: { + type: Sequelize.DataTypes.STRING + }, + created_at: { + type: Sequelize.DataTypes.DATE + }, + updated_at: { + type: Sequelize.DataTypes.DATE + } + }); + const webhooksEvents = client.define('webhook_event', { + id: { + type: Sequelize.DataTypes.UUID, + primaryKey: true + }, + name: { + type: Sequelize.DataTypes.TEXT('medium') + } + }); + + webhooksSchema.belongsToMany(webhooksEvents, { + through: 'webhook_event_mapping', + as: 'events', + foreignKey: 'webhook_id' + }); + webhooksEvents.belongsToMany(webhooksSchema, { + through: 'webhook_event_mapping', + as: 'webhooks', + foreignKey: 'webhook_event_id' + }); + + await webhooksSchema.sync(); + await webhooksEvents.sync(); +} diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js new file mode 100644 index 000000000..0fbe56dd9 --- /dev/null +++ b/src/webhooks/models/webhookManager.js @@ -0,0 +1,8 @@ +'use strict'; + +const databaseConnector = require('./database/databaseConnector'); + +module.exports.getAllWebhooks = async function () { + let getAllWebhooks = await databaseConnector.getAllWebhooks(); + return getAllWebhooks; +}; diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js new file mode 100644 index 000000000..9a5a24ec4 --- /dev/null +++ b/src/webhooks/routes/webhooksRouter.js @@ -0,0 +1,11 @@ +'use strict'; + +let swaggerValidator = require('express-ajv-swagger-validation'); +let express = require('express'); +let router = express.Router(); + +let webhooksController = require('../controllers/webhooksController'); + +router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); + +module.exports = router; diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index e96f8d394..16b5b1c0e 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -80,7 +80,7 @@ describe('Sequelize client tests', function () { describe('Init tests', () => { it('it should initialize sequelize with mysql client successfully', async () => { await sequelizeConnector.init(sequelizeStub()); - should(sequelizeDefineStub.calledThrice).eql(true); + should(sequelizeDefineStub.calledTwice).eql(true); }); }); diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js new file mode 100644 index 000000000..f0adad6a9 --- /dev/null +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -0,0 +1,95 @@ +'use strict'; +const sinon = require('sinon'), + { expect } = require('chai'), + databaseConfig = require('../../../../src/config/databaseConfig'), + sequelizeConnector = require('../../../../src/webhooks/models/database/sequelize/sequelizeConnector'); + +describe('Sequelize client tests', function () { + const webhookRaw = { + id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', + name: 'my special webhook', + url: 'http://callback.com', + global: false, + format_type: 'json', + created_at: '2020-06-13T13:13:16.763Z', + updated_at: '2020-06-13T13:13:16.763Z', + events: [] + }; + + let sandbox, + sequelizeModelStub, + sequelizeDeleteStub, + sequelizeDefineStub, + sequelizeGeValueStub, + sequelizeGetStub, + sequelizeCreateStub, + sequelizeUpdateStub, + sequelizeBelongsToMany; + + before(async () => { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(async () => { + databaseConfig.type = 'SQLITE'; + databaseConfig.name = 'predator'; + databaseConfig.username = 'username'; + databaseConfig.password = 'password'; + + sequelizeModelStub = sandbox.stub(); + sequelizeDefineStub = sandbox.stub(); + sequelizeGetStub = sandbox.stub(); + sequelizeDeleteStub = sandbox.stub(); + sequelizeGeValueStub = sandbox.stub(); + sequelizeCreateStub = sandbox.stub(); + sequelizeUpdateStub = sandbox.stub(); + sequelizeBelongsToMany = sandbox.stub(); + + sequelizeDefineStub.returns({ + hasMany: () => { + }, + sync: () => { + }, + belongsToMany: () => { + + } + }); + + sequelizeModelStub.returns({ + key: {}, + value: {}, + findAll: sequelizeGetStub, + findOne: sequelizeGeValueStub, + destroy: sequelizeDeleteStub, + create: sequelizeCreateStub + }); + + await sequelizeConnector.init({ + model: sequelizeModelStub, + define: sequelizeDefineStub + }); + }); + + afterEach(() => { + sandbox.resetHistory(); + }); + + after(() => { + sandbox.restore(); + }); + + describe('getAllWebhooks', function() { + describe('Happy flow', function() { + it('expect return an array with 1 webhook', async function() { + sequelizeGetStub.resolves([webhookRaw]); + const webhooks = await sequelizeConnector.getAllWebhooks(); + + expect(sequelizeGetStub.calledOnce).to.be.equal(true); + expect(sequelizeGetStub.args[0][0]).to.be.deep.equal({ include: ['events'] }); + + expect(webhooks).to.be.an('array').and.have.lengthOf(1); + expect(webhooks[0]).to.be.deep.equal(webhookRaw); + }); + }); + }); +}); From 0e831afbd4815c21c86f5f9c10d2a62bf10888d6 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 22 Jun 2020 11:50:59 +0300 Subject: [PATCH 14/38] feat(webhooks): implement POST /webhooks (#319) --- .eslintrc.json | 2 +- docs/openapi3.yaml | 2 + package-lock.json | 526 +++++++++++++----- package.json | 4 +- .../controllers/webhooksController.js | 10 + .../database/cassandra/cassandraConnector.js | 5 + .../models/database/databaseConnector.js | 5 + .../database/sequelize/sequelizeConnector.js | 38 +- src/webhooks/models/webhookManager.js | 12 + src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 41 ++ .../webhooks/webhooks-test.js | 142 +++++ .../jobs/sequelize/sequelizeConnector-test.js | 6 +- .../sequelize/sequelizeConnector-test.js | 60 +- 14 files changed, 705 insertions(+), 149 deletions(-) create mode 100644 tests/integration-tests/webhooks/helpers/requestCreator.js create mode 100644 tests/integration-tests/webhooks/webhooks-test.js diff --git a/.eslintrc.json b/.eslintrc.json index 6f95b6671..4c0ddf4ef 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 8, + "ecmaVersion": 2018, "sourceType": "module" }, "rules": { diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index f71549fe7..69b134056 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -2529,6 +2529,8 @@ components: events: description: list of events which will trigger the webhook type: array + uniqueItems: true + minItems: 1 items: $ref: '#/components/schemas/webhooks_types' format_type: diff --git a/package-lock.json b/package-lock.json index 4d72c0ab0..49f7f648a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ }, "@babel/generator": { "version": "7.9.4", - "resolved": "http://npm.zooz.co:8083/@babel%2fgenerator/-/generator-7.9.4.tgz", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { @@ -101,7 +101,7 @@ }, "@babel/helper-validator-identifier": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", "dev": true }, @@ -149,7 +149,7 @@ }, "@babel/parser": { "version": "7.9.4", - "resolved": "http://npm.zooz.co:8083/@babel%2fparser/-/parser-7.9.4.tgz", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", "dev": true }, @@ -172,7 +172,7 @@ }, "@babel/template": { "version": "7.8.6", - "resolved": "http://npm.zooz.co:8083/@babel%2ftemplate/-/template-7.8.6.tgz", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { @@ -183,7 +183,7 @@ }, "@babel/traverse": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2ftraverse/-/traverse-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { @@ -217,7 +217,7 @@ }, "@babel/types": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2ftypes/-/types-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { @@ -745,9 +745,9 @@ } }, "acorn": { - "version": "7.1.1", - "resolved": "http://npm.zooz.co:8083/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", "dev": true }, "acorn-jsx": { @@ -821,7 +821,7 @@ }, "ansi-colors": { "version": "3.2.3", - "resolved": "http://npm.zooz.co:8083/ansi-colors/-/ansi-colors-3.2.3.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, @@ -864,7 +864,7 @@ }, "anymatch": { "version": "3.1.1", - "resolved": "http://npm.zooz.co:8083/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { @@ -874,7 +874,7 @@ "dependencies": { "normalize-path": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1363,7 +1363,7 @@ }, "binary-extensions": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/binary-extensions/-/binary-extensions-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, @@ -1511,7 +1511,7 @@ }, "braces": { "version": "3.0.2", - "resolved": "http://npm.zooz.co:8083/braces/-/braces-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { @@ -1825,7 +1825,7 @@ }, "chokidar": { "version": "3.3.0", - "resolved": "http://npm.zooz.co:8083/chokidar/-/chokidar-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { @@ -1841,13 +1841,13 @@ "dependencies": { "is-extglob": { "version": "2.1.1", - "resolved": "http://npm.zooz.co:8083/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-glob": { "version": "4.0.1", - "resolved": "http://npm.zooz.co:8083/is-glob/-/is-glob-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { @@ -1856,7 +1856,7 @@ }, "normalize-path": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1914,9 +1914,9 @@ "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==" }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, "cliui": { @@ -4242,22 +4242,22 @@ } }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz", + "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", @@ -4270,67 +4270,71 @@ "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", + "levn": "^0.4.1", "lodash": "^4.17.14", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.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", "v8-compile-cache": "^2.0.3" }, "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==", + "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": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "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": { - "color-convert": "^1.9.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, + "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 + }, "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": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "debug": { @@ -4342,6 +4346,27 @@ "ms": "^2.1.1" } }, + "eslint-utils": { + "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.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", + "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 + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4357,19 +4382,76 @@ "is-extglob": "^2.1.1" } }, + "levn": { + "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.2.1", + "type-check": "~0.4.0" + } + }, + "optionator": { + "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.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "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": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "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==", + "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-json-comments": { @@ -4379,12 +4461,30 @@ "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" + } + }, + "type-check": { + "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.2.1" + } + }, + "which": { + "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" } } } @@ -4604,9 +4704,9 @@ "dev": true }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4629,14 +4729,22 @@ "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", + "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", "dev": true, "requires": { - "acorn": "^7.1.1", + "acorn": "^7.2.0", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^1.2.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", + "dev": true + } } }, "esprima": { @@ -4645,18 +4753,18 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", - "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", + "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": "^5.0.0" + "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", - "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", "dev": true } } @@ -5026,7 +5134,7 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "http://npm.zooz.co:8083/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { @@ -5351,7 +5459,7 @@ }, "fsevents": { "version": "2.1.2", - "resolved": "http://npm.zooz.co:8083/fsevents/-/fsevents-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true @@ -6114,7 +6222,7 @@ }, "he": { "version": "1.2.0", - "resolved": "http://npm.zooz.co:8083/he/-/he-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, @@ -6385,9 +6493,9 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.2.0.tgz", + "integrity": "sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -6573,7 +6681,7 @@ }, "is-binary-path": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { @@ -6666,7 +6774,7 @@ }, "is-finite": { "version": "1.1.0", - "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", "dev": true }, @@ -6707,7 +6815,7 @@ }, "is-number": { "version": "7.0.0", - "resolved": "http://npm.zooz.co:8083/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, @@ -7168,7 +7276,7 @@ }, "kind-of": { "version": "6.0.3", - "resolved": "http://npm.zooz.co:8083/kind-of/-/kind-of-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "latest-version": { @@ -7849,7 +7957,7 @@ }, "minimist": { "version": "1.2.5", - "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.5.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { @@ -7931,7 +8039,7 @@ }, "mocha": { "version": "7.1.1", - "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-7.1.1.tgz", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { @@ -7963,7 +8071,7 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", - "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -7972,7 +8080,7 @@ }, "chalk": { "version": "2.4.2", - "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-2.4.2.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -7983,7 +8091,7 @@ "dependencies": { "supports-color": { "version": "5.5.0", - "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-5.5.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -7994,7 +8102,7 @@ }, "debug": { "version": "3.2.6", - "resolved": "http://npm.zooz.co:8083/debug/-/debug-3.2.6.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { @@ -8003,7 +8111,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "http://npm.zooz.co:8083/glob/-/glob-7.1.3.tgz", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { @@ -8017,7 +8125,7 @@ }, "log-symbols": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/log-symbols/-/log-symbols-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { @@ -8026,7 +8134,7 @@ }, "mkdirp": { "version": "0.5.3", - "resolved": "http://npm.zooz.co:8083/mkdirp/-/mkdirp-0.5.3.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { @@ -8035,13 +8143,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "http://npm.zooz.co:8083/ms/-/ms-2.1.1.tgz", + "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": "http://npm.zooz.co:8083/supports-color/-/supports-color-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { @@ -8460,7 +8568,7 @@ }, "node-environment-flags": { "version": "1.0.6", - "resolved": "http://npm.zooz.co:8083/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { @@ -8772,7 +8880,7 @@ }, "object.getownpropertydescriptors": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { @@ -9172,7 +9280,7 @@ }, "picomatch": { "version": "2.2.2", - "resolved": "http://npm.zooz.co:8083/picomatch/-/picomatch-2.2.2.tgz", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, @@ -9572,7 +9680,7 @@ }, "readdirp": { "version": "3.2.0", - "resolved": "http://npm.zooz.co:8083/readdirp/-/readdirp-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { @@ -10000,11 +10108,170 @@ }, "rewire": { "version": "5.0.0", - "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { "eslint": "^6.8.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 + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "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": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "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" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.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.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "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-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" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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" + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "rimraf": { @@ -10017,13 +10284,10 @@ } }, "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true }, "run-node": { "version": "1.0.0", @@ -11613,7 +11877,7 @@ }, "to-regex-range": { "version": "5.0.1", - "resolved": "http://npm.zooz.co:8083/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { @@ -11918,9 +12182,9 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "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 }, "valid-url": { @@ -12212,7 +12476,7 @@ }, "yargs-unparser": { "version": "1.6.0", - "resolved": "http://npm.zooz.co:8083/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { diff --git a/package.json b/package.json index a7fa24e6d..599448f43 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,8 @@ "@commitlint/config-conventional": "^9.0.1", "chai": "^4.2.0", "commitlint": "^9.0.1", - "cz-conventional-changelog": "^3.2.0", - "eslint": "^6.2.2", + "cz-conventional-changelog": "^2.1.0", + "eslint": "^7.2.0", "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-node": "^9.1.0", diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index aa068ad11..cae0a4cf7 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -10,3 +10,13 @@ module.exports.getAllWebhooks = async function (req, res, next) { return next(err); } }; + +module.exports.createWebhook = async function (req, res, next) { + let webhook; + try { + webhook = await webhookManager.createWebhook(req.body); + return res.status(201).json(webhook); + } catch (err) { + return next(err); + } +}; \ No newline at end of file diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index b22b54435..432a77fd3 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -2,6 +2,7 @@ let logger = require('../../../../common/logger'); module.exports = { init, + createWebhook, getAllWebhooks }; @@ -14,3 +15,7 @@ async function init() { async function getAllWebhooks() { throw new Error('Not implemented.'); } + +async function createWebhook() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index de1c28e3f..27e1949d9 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -5,6 +5,7 @@ let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cass module.exports = { init, getAllWebhooks, + createWebhook, closeConnection }; @@ -18,4 +19,8 @@ function closeConnection() { async function getAllWebhooks(from, limit, exclude) { return databaseConnector.getAllWebhooks(from, limit, exclude); +} + +async function createWebhook(webhook) { + return databaseConnector.createWebhook(webhook); } \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 0045016b4..1e10da838 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -1,11 +1,21 @@ 'use strict'; const Sequelize = require('sequelize'); +const uuid = require('uuid'); + let client; module.exports = { init, - getAllWebhooks + getAllWebhooks, + createWebhook +}; + +function parseWebhook(webhookRecord) { + return { + ...webhookRecord.dataValues, + events: webhookRecord.events && webhookRecord.events.map(eventRecord => eventRecord.dataValues.name) + }; }; async function init(sequelizeClient) { @@ -15,7 +25,31 @@ async function init(sequelizeClient) { async function getAllWebhooks() { const webhooksModel = client.model('webhook'); - return webhooksModel.findAll({ include: ['events'] }); + const webhooks = await webhooksModel.findAll({ include: ['events'] }); + return webhooks.map(parseWebhook); +} + +async function createWebhook(webhook) { + const id = uuid.v4(); + const webhooksModel = client.model('webhook'); + const webhooksEvents = client.model('webhook_event'); + const events = await webhooksEvents.findAll({ where: { name: webhook.events } }); + const eventsIds = events.map(({ id }) => id); + const webhookToInsert = { + id, + name: webhook.name, + url: webhook.url, + format_type: webhook.format_type, + global: webhook.global + }; + await client.transaction(async function(transaction) { + const createdWebhook = await webhooksModel.create(webhookToInsert, { transaction }); + await createdWebhook.setEvents(eventsIds, { transaction }); + return createdWebhook; + }); + const retrievedWebhook = await webhooksModel.findByPk(id, { include: ['events'] }); + const parsedWebhook = parseWebhook(retrievedWebhook); + return parsedWebhook; } async function initSchemas() { diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 0fbe56dd9..4d0133f02 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -2,7 +2,19 @@ const databaseConnector = require('./database/databaseConnector'); +const webhookDefaultValues = { + global: false +}; + module.exports.getAllWebhooks = async function () { let getAllWebhooks = await databaseConnector.getAllWebhooks(); return getAllWebhooks; }; + +module.exports.createWebhook = async function(webhookInfo) { + const webhook = { + ...webhookDefaultValues, + ...webhookInfo + }; + return databaseConnector.createWebhook(webhook); +}; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 9a5a24ec4..a9e59ab1e 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -7,5 +7,6 @@ let router = express.Router(); let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); +router.post('/', swaggerValidator.validate, webhooksController.createWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js new file mode 100644 index 000000000..0db7b19b5 --- /dev/null +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -0,0 +1,41 @@ + +const request = require('supertest'); +const expressApp = require('../../../../src/app'); + +let app; +const headers = { 'Content-Type': 'application/json' }; +const resourceUri = '/v1/webhooks'; + +module.exports = { + init, + createWebhook, + getWebhooks +}; + +async function init() { + try { + app = await expressApp(); + } catch (err){ + console.log(err); + process.exit(1); + } +} + +function createWebhook(body) { + return request(app) + .post(resourceUri) + .send(body) + .set(headers) + .expect(function(res){ + return res; + }); +} + +function getWebhooks() { + return request(app) + .get(resourceUri) + .set(headers) + .expect(function (res) { + return res; + }); +} \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js new file mode 100644 index 000000000..eb13793a9 --- /dev/null +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -0,0 +1,142 @@ +const { expect } = require('chai'); +const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); + +const webhookRequestSender = require('./helpers/requestCreator'); + +describe('Webhooks api', function () { + this.timeout(5000000); + before(async function () { + await webhookRequestSender.init(); + }); + + describe('Good requests', async function () { + describe('GET /v1/webhooks', async function () { + const numOfWebhooksToInsert = 5; + it(`return ${numOfWebhooksToInsert} webhooks`, async function() { + const webhooksToInsert = (new Array(numOfWebhooksToInsert)) + .fill(0, 0, numOfWebhooksToInsert) + .map(index => generateWebhook(`My webhook ${index}`)); + await Promise.all(webhooksToInsert.map(webhook => webhookRequestSender.createWebhook(webhook))); + + const webhooksGetResponse = await webhookRequestSender.getWebhooks(); + expect(webhooksGetResponse.statusCode).to.equal(200); + + const webhooks = webhooksGetResponse.body; + expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); + }); + }); + describe('POST /v1/webhooks', function () { + it('Create webhook and response 201 status code', async function() { + const webhook = generateWebhook(); + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + it('Create webhook with global=true and response 201 status code', async function() { + const webhook = generateWebhook(); + webhook.global = true; + + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + it('Create webhook with unspecified global field, expect default value and response 201 status code', async function() { + const webhook = generateWebhook(); + const { global, ...webhookWithoutGlobal } = webhook; + + let createWebhookResponse = await webhookRequestSender.createWebhook(webhookWithoutGlobal); + + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + }); + }); + + describe('Bad requests', function () { + describe('POST /v1/webhooks', function () { + describe('name validation', function() { + it('Create webhook with bad type of name', async function () { + const webhook = generateWebhook(5); + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('format_type validation', function() { + it('Create webhook with bad format_type', async function () { + const webhook = generateWebhook(); + webhook.format_type = 'TOTTALLY NOT A VALID FORMAT TYPE lalalalalla'; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('global validation', function() { + it('Create webhook with global not a boolean', async function () { + const webhook = generateWebhook(); + webhook.global = 'TOTTALLY NOT A VALID GLOBAL'; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('events validation', function() { + it('Create webhook with empty events', async function () { + const webhook = generateWebhook(); + webhook.events = []; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with invalid event name', async function () { + const webhook = generateWebhook('My special webhook', 'https://url.com/callback', ['bad_value']); + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with duplicate event name', async function () { + const webhook = generateWebhook(); + webhook.events = [webhook.events[0], webhook.events[0]]; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with too many valid values', async function () { + const webhook = generateWebhook(); + webhook.events = [...webhook.events, webhook.events[0]]; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + }); + }); +}); + +function generateWebhook(name = 'My webhook', url = 'https://humus.is.love/callback', events = WEBHOOK_EVENT_TYPES) { + return { + name, + url, + events, + global: false, + format_type: EVENT_FORMAT_TYPE_JSON + }; +} + +function assertDeepWebhookEquality(webhook, webhookFromAPI) { + const { + id, + events, + ...restOfWebhook + } = webhook; + const { + id: webhookFromAPIId, + events: webhookFromAPIEvents, + created_at: createdAt, + updated_at: updatedAt, + ...restwebhookFromAPI + } = webhookFromAPI; + expect(restOfWebhook).to.deep.equal(restwebhookFromAPI); + expect(events).to.have.members(webhookFromAPIEvents); +} \ No newline at end of file diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index 16b5b1c0e..a70371998 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -45,12 +45,14 @@ describe('Sequelize client tests', function () { hasMany: () => { }, sync: () => { - } + }, + belongsToMany: () => {} }); sequelizeModelStub.returns({ email: {}, webhook: {}, + belongsToMany: () => {}, create: sequelizeCreateStub, update: sequelizeUpdateStub, findAll: sequelizeGetStub, @@ -398,7 +400,7 @@ describe('Sequelize client tests', function () { }, 'include': [ {}, - {} + 'webhooks' ], 'where': { 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js index f0adad6a9..028e2d144 100644 --- a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -4,15 +4,20 @@ const sinon = require('sinon'), databaseConfig = require('../../../../src/config/databaseConfig'), sequelizeConnector = require('../../../../src/webhooks/models/database/sequelize/sequelizeConnector'); +const { WEBHOOK_EVENT_TYPES } = require('../../../../src/common/consts'); +const uuid = require('uuid'); + describe('Sequelize client tests', function () { const webhookRaw = { - id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', - name: 'my special webhook', - url: 'http://callback.com', - global: false, - format_type: 'json', - created_at: '2020-06-13T13:13:16.763Z', - updated_at: '2020-06-13T13:13:16.763Z', + dataValues: { + id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', + name: 'my special webhook', + url: 'http://callback.com', + global: false, + format_type: 'json', + created_at: '2020-06-13T13:13:16.763Z', + updated_at: '2020-06-13T13:13:16.763Z', + }, events: [] }; @@ -24,7 +29,8 @@ describe('Sequelize client tests', function () { sequelizeGetStub, sequelizeCreateStub, sequelizeUpdateStub, - sequelizeBelongsToMany; + sequelizeBelongsToMany, + sequelizeTransactionStub; before(async () => { sandbox = sinon.sandbox.create(); @@ -44,6 +50,7 @@ describe('Sequelize client tests', function () { sequelizeCreateStub = sandbox.stub(); sequelizeUpdateStub = sandbox.stub(); sequelizeBelongsToMany = sandbox.stub(); + sequelizeTransactionStub = sandbox.stub(); sequelizeDefineStub.returns({ hasMany: () => { @@ -61,12 +68,14 @@ describe('Sequelize client tests', function () { findAll: sequelizeGetStub, findOne: sequelizeGeValueStub, destroy: sequelizeDeleteStub, - create: sequelizeCreateStub + create: sequelizeCreateStub, + findByPk: sequelizeGetStub }); await sequelizeConnector.init({ model: sequelizeModelStub, - define: sequelizeDefineStub + define: sequelizeDefineStub, + transaction: sequelizeTransactionStub }); }); @@ -88,7 +97,36 @@ describe('Sequelize client tests', function () { expect(sequelizeGetStub.args[0][0]).to.be.deep.equal({ include: ['events'] }); expect(webhooks).to.be.an('array').and.have.lengthOf(1); - expect(webhooks[0]).to.be.deep.equal(webhookRaw); + expect(webhooks[0]).to.be.deep.contain(webhookRaw.dataValues); + expect(webhooks[0].events).to.be.deep.equal(webhookRaw.events); + }); + }); + }); + + describe('createWebhook', function() { + describe('Happy flow', function() { + it('expect to return a webhook with a valid uuid', async function() { + const events = [WEBHOOK_EVENT_TYPES[0], WEBHOOK_EVENT_TYPES[1]]; + const eventRecords = events.map(event => ({dataValues: {id: uuid(), name: event}})); + const webhook = { + ...webhookRaw.dataValues, + events + }; + const webhookWithEvents = { + dataValues: { + ...webhookRaw.dataValues + }, + events: eventRecords + }; + sequelizeGetStub.onFirstCall().resolves(eventRecords); + sequelizeGetStub.onSecondCall().resolves(webhookWithEvents); + sequelizeTransactionStub.resolves(); + const createdWebhook = await sequelizeConnector.createWebhook(webhook); + + expect(createdWebhook).to.be.an('object'); + expect(createdWebhook).to.be.deep.contain(webhookWithEvents.dataValues); + expect(createdWebhook.events).to.be.deep.equal(events); + expect(createdWebhook).to.have.a.property('id'); }); }); }); From e40f4deee6af582b11bfe765225fe6bbe4269897 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 22 Jun 2020 12:44:29 +0300 Subject: [PATCH 15/38] implement GET /webhooks/:webhook_id (#320) --- docs/openapi3.yaml | 18 ++++++++++ .../controllers/webhooksController.js | 11 ++++++ .../models/database/databaseConnector.js | 6 ++++ .../database/sequelize/sequelizeConnector.js | 11 ++++-- src/webhooks/models/webhookManager.js | 11 ++++++ src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 11 ++++++ .../webhooks/webhooks-test.js | 35 +++++++++++++++++++ 8 files changed, 101 insertions(+), 3 deletions(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 69b134056..48222111f 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -1454,6 +1454,15 @@ paths: - Webhooks summary: Retrieve a webhook by id description: Retrieve a webhook by id. + parameters: + - in: path + name: webhook_id + description: The webhook id. + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 responses: '200': description: Success @@ -1479,6 +1488,15 @@ paths: - Webhooks summary: Update a webhook description: Update a webhook. + parameters: + - in: path + name: webhook_id + description: The webhook id. + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 responses: '200': description: Success diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index cae0a4cf7..7d7d8d323 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -11,6 +11,17 @@ module.exports.getAllWebhooks = async function (req, res, next) { } }; +module.exports.getWebhook = async function (req, res, next) { + let webhook; + let webhookId = req.params.webhook_id; + try { + webhook = await webhookManager.getWebhook(webhookId); + return res.status(200).json(webhook); + } catch (err) { + return next(err); + } +}; + module.exports.createWebhook = async function (req, res, next) { let webhook; try { diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 27e1949d9..3bae86155 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -2,10 +2,12 @@ let databaseConfig = require('../../../config/databaseConfig'); let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; + module.exports = { init, getAllWebhooks, createWebhook, + getWebhook, closeConnection }; @@ -23,4 +25,8 @@ async function getAllWebhooks(from, limit, exclude) { async function createWebhook(webhook) { return databaseConnector.createWebhook(webhook); +} + +async function getWebhook(webhookId) { + return databaseConnector.getWebhook(webhookId); } \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 1e10da838..e41678e34 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -1,5 +1,3 @@ -'use strict'; - const Sequelize = require('sequelize'); const uuid = require('uuid'); @@ -8,7 +6,8 @@ let client; module.exports = { init, getAllWebhooks, - createWebhook + createWebhook, + getWebhook }; function parseWebhook(webhookRecord) { @@ -29,6 +28,12 @@ async function getAllWebhooks() { return webhooks.map(parseWebhook); } +async function getWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + const webhook = await webhooksModel.findByPk(webhookId, { include: ['events'] }); + return parseWebhook(webhook); +} + async function createWebhook(webhook) { const id = uuid.v4(); const webhooksModel = client.model('webhook'); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 4d0133f02..6dc4bfa65 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -1,6 +1,7 @@ 'use strict'; const databaseConnector = require('./database/databaseConnector'); +const { ERROR_MESSAGES } = require('../../common/consts'); const webhookDefaultValues = { global: false @@ -11,6 +12,16 @@ module.exports.getAllWebhooks = async function () { return getAllWebhooks; }; +module.exports.getWebhook = async function (webhookId) { + const webhook = await databaseConnector.getWebhook(webhookId); + if (!webhook) { + const error = new Error(ERROR_MESSAGES.NOT_FOUND); + error.statusCode = 404; + throw error; + } + return webhook; +}; + module.exports.createWebhook = async function(webhookInfo) { const webhook = { ...webhookDefaultValues, diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index a9e59ab1e..79f23d48f 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -8,5 +8,6 @@ let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); +router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index 0db7b19b5..7c3330527 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -9,6 +9,8 @@ const resourceUri = '/v1/webhooks'; module.exports = { init, createWebhook, + getWebhooks, + getWebhook getWebhooks }; @@ -38,4 +40,13 @@ function getWebhooks() { .expect(function (res) { return res; }); +} + +function getWebhook(webhookdId) { + return request(app) + .get(`${resourceUri}/${webhookdId}`) + .set(headers) + .expect(function (res) { + return res; + }); } \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index eb13793a9..bc290f25c 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -1,4 +1,6 @@ const { expect } = require('chai'); +const uuid = require('uuid'); + const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); const webhookRequestSender = require('./helpers/requestCreator'); @@ -25,6 +27,19 @@ describe('Webhooks api', function () { expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); }); }); + describe('GET /webhook/:webhook_id', function () { + it('should retrieve the webhook that was created', async function() { + const webhook = generateWebhook(); + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(createWebhookResponse.statusCode).to.equal(201); + + const webhookId = createWebhookResponse.body.id; + const getWebhookResponse = await webhookRequestSender.getWebhook(webhookId); + + expect(getWebhookResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(webhook, getWebhookResponse.body); + }); + }); describe('POST /v1/webhooks', function () { it('Create webhook and response 201 status code', async function() { const webhook = generateWebhook(); @@ -111,6 +126,26 @@ describe('Webhooks api', function () { }); }); }); + describe('GET /webhook/:webhook_id', function () { + it('should return 400 for bad uuid', async function() { + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.getWebhook(badWebhookValue); + + expect(response.statusCode).to.equal(400); + }); + }); + }); + describe('Sad requests', function() { + describe('GET /webhook/:webhook_id', function () { + it('should return 404 for no existing webhook', async function() { + const notExistingWebhookId = uuid.v4(); + + const response = await webhookRequestSender.getWebhook(notExistingWebhookId); + + expect(response.statusCode).to.equal(404); + }); + }); }); }); From 2a3d85591115a6d096f9c735b71c4cb4fea84b4c Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 29 Jun 2020 20:49:57 +0300 Subject: [PATCH 16/38] feat(webhooks): implement DELETE /webhooks/:webhook_id (#325) --- docs/openapi3.yaml | 31 +++++++++++++++- .../database/sequelize/sequelizeConnector.js | 3 +- .../controllers/webhooksController.js | 10 +++++ .../models/database/databaseConnector.js | 7 +++- .../database/sequelize/sequelizeConnector.js | 17 +++++++-- src/webhooks/models/webhookManager.js | 4 ++ src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 18 ++++++--- .../webhooks/webhooks-test.js | 37 +++++++++++++++++-- .../sequelize/sequelizeConnector-test.js | 16 ++++++++ 10 files changed, 129 insertions(+), 15 deletions(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 48222111f..7beb2b003 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -1522,6 +1522,35 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + delete: + operationId: delete-webhook + tags: + - Webhooks + summary: Delete webhook file + description: Delete a specific webhook by id. + parameters: + - in: path + name: webhook_id + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 + responses: + '204': + description: Success + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' # Files /v1/files: @@ -2523,8 +2552,6 @@ components: filename: type: string description: the name of the file - type: number - description: benchmark percentage weight webhook: type: object required: diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index c56c18c55..7ac38a393 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -179,7 +179,8 @@ async function initSchemas() { webhooks.belongsToMany(job, { through: 'webhook_job_mapping', as: 'jobs', - foreignKey: 'webhook_id' + foreignKey: 'webhook_id', + onDelete: 'CASCADE' }); job.belongsToMany(webhooks, { through: 'webhook_job_mapping', diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index 7d7d8d323..a957c8349 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -30,4 +30,14 @@ module.exports.createWebhook = async function (req, res, next) { } catch (err) { return next(err); } +}; + +module.exports.deleteWebhook = async function (req, res, next) { + let webhookId = req.params.webhook_id; + try { + await webhookManager.deleteWebhook(webhookId); + return res.status(204).json(); + } catch (err) { + return next(err); + } }; \ No newline at end of file diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 3bae86155..b63c53018 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -8,6 +8,7 @@ module.exports = { getAllWebhooks, createWebhook, getWebhook, + deleteWebhook, closeConnection }; @@ -29,4 +30,8 @@ async function createWebhook(webhook) { async function getWebhook(webhookId) { return databaseConnector.getWebhook(webhookId); -} \ No newline at end of file +} + +async function deleteWebhook(webhookId) { + return databaseConnector.deleteWebhook(webhookId); +}; \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index e41678e34..f526c9137 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -7,11 +7,12 @@ module.exports = { init, getAllWebhooks, createWebhook, - getWebhook + getWebhook, + deleteWebhook }; function parseWebhook(webhookRecord) { - return { + return webhookRecord && { ...webhookRecord.dataValues, events: webhookRecord.events && webhookRecord.events.map(eventRecord => eventRecord.dataValues.name) }; @@ -57,6 +58,15 @@ async function createWebhook(webhook) { return parsedWebhook; } +async function deleteWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + return webhooksModel.destroy({ + where: { + id: webhookId + } + }); +} + async function initSchemas() { const webhooksSchema = client.define('webhook', { id: { @@ -95,7 +105,8 @@ async function initSchemas() { webhooksSchema.belongsToMany(webhooksEvents, { through: 'webhook_event_mapping', as: 'events', - foreignKey: 'webhook_id' + foreignKey: 'webhook_id', + onDelete: 'CASCADE' }); webhooksEvents.belongsToMany(webhooksSchema, { through: 'webhook_event_mapping', diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 6dc4bfa65..2be6f9986 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -28,4 +28,8 @@ module.exports.createWebhook = async function(webhookInfo) { ...webhookInfo }; return databaseConnector.createWebhook(webhook); +}; + +module.exports.deleteWebhook = async function(webhookId) { + return databaseConnector.deleteWebhook(webhookId); }; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 79f23d48f..1e53ae499 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -9,5 +9,6 @@ let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); +router.delete('/:webhook_id', swaggerValidator.validate, webhooksController.deleteWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index 7c3330527..ae3d53ba0 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -1,4 +1,3 @@ - const request = require('supertest'); const expressApp = require('../../../../src/app'); @@ -10,8 +9,8 @@ module.exports = { init, createWebhook, getWebhooks, - getWebhook - getWebhooks + getWebhook, + deleteWebhook }; async function init() { @@ -42,9 +41,18 @@ function getWebhooks() { }); } -function getWebhook(webhookdId) { +function getWebhook(webhookId) { + return request(app) + .get(`${resourceUri}/${webhookId}`) + .set(headers) + .expect(function (res) { + return res; + }); +} + +function deleteWebhook(webhookId) { return request(app) - .get(`${resourceUri}/${webhookdId}`) + .delete(`${resourceUri}/${webhookId}`) .set(headers) .expect(function (res) { return res; diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index bc290f25c..f2df0f9f3 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -27,7 +27,7 @@ describe('Webhooks api', function () { expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); }); }); - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should retrieve the webhook that was created', async function() { const webhook = generateWebhook(); let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); @@ -66,6 +66,27 @@ describe('Webhooks api', function () { assertDeepWebhookEquality(webhook, createWebhookResponse.body); }); }); + describe('DELETE /v1/webhooks/:webhook_id', function() { + it('Create a webhook -> deleting it -> expecting to get 404 in GET', async function() { + const webhook = generateWebhook(); + + const insertWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertWebhookResponse.statusCode).to.equal(201); + const webhookId = insertWebhookResponse.body.id; + + const deleteWebhookResponse = await webhookRequestSender.deleteWebhook(webhookId); + expect(deleteWebhookResponse.statusCode).to.equal(204); + + const getWebhookResponse = await webhookRequestSender.getWebhook(webhookId); + expect(getWebhookResponse.statusCode).to.equal(404); + }); + it('Deleting unexisting webhook -> expect 204 status code', async function() { + const id = uuid.v4(); + + const deleteWebhookResponse = await webhookRequestSender.deleteWebhook(id); + expect(deleteWebhookResponse.statusCode).to.equal(204); + }); + }); }); describe('Bad requests', function () { @@ -126,18 +147,28 @@ describe('Webhooks api', function () { }); }); }); - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should return 400 for bad uuid', async function() { const badWebhookValue = 'lalallalalal'; const response = await webhookRequestSender.getWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); + }); + }); + describe('DELETE /v1/webhooks/:webhook_id', function () { + it('should return 400 for bad uuid', async function() { + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.deleteWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); }); }); }); + describe('Sad requests', function() { - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should return 404 for no existing webhook', async function() { const notExistingWebhookId = uuid.v4(); diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js index 028e2d144..5c5c4b004 100644 --- a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -130,4 +130,20 @@ describe('Sequelize client tests', function () { }); }); }); + describe('deleteWebhook', function() { + describe('Happy flow', function() { + it('expect to delete by query with proper webhook_id', async function() { + const id = uuid.v4(); + const queryOptions = { where: { id } }; + + sequelizeDeleteStub.resolves(); + await sequelizeConnector.deleteWebhook(id); + + expect(sequelizeDeleteStub.calledOnce).to.equal(true); + + const destroyOptions = sequelizeDeleteStub.args[0][0]; + expect(destroyOptions).to.deep.equal(queryOptions); + }); + }); + }); }); From 0ab8d4762efeefa76ba05de63d466c528a6441a5 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 5 Jul 2020 13:22:53 +0300 Subject: [PATCH 17/38] feat(webhooks): implement PUT /webhooks/:webhook_id --- src/common/generateError.js | 6 + src/processors/models/processorsManager.js | 23 ++-- .../controllers/webhooksController.js | 10 ++ .../database/cassandra/cassandraConnector.js | 7 +- .../models/database/databaseConnector.js | 5 + .../database/sequelize/sequelizeConnector.js | 24 +++- src/webhooks/models/webhookManager.js | 29 +++-- src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 13 +- .../webhooks/webhooks-test.js | 111 +++++++++++++++++- 10 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 src/common/generateError.js diff --git a/src/common/generateError.js b/src/common/generateError.js new file mode 100644 index 000000000..411215b7e --- /dev/null +++ b/src/common/generateError.js @@ -0,0 +1,6 @@ + +module.exports = function(stautsCode, message) { + const error = new Error(message); + error.statusCode = stautsCode; + return error; +}; \ No newline at end of file diff --git a/src/processors/models/processorsManager.js b/src/processors/models/processorsManager.js index f5a853b52..2bbecec5b 100644 --- a/src/processors/models/processorsManager.js +++ b/src/processors/models/processorsManager.js @@ -5,12 +5,13 @@ const uuid = require('uuid'); const logger = require('../../common/logger'), databaseConnector = require('./database/databaseConnector'), testsManager = require('../../tests/models/manager'), - { ERROR_MESSAGES } = require('../../common/consts'); + { ERROR_MESSAGES } = require('../../common/consts'), + generateError = require('../../common/generateError'); module.exports.createProcessor = async function (processor) { const processorWithTheSameName = await databaseConnector.getProcessorByName(processor.name); if (processorWithTheSameName) { - throw generateError(ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST, 400); + throw generateError(400, ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST); } let processorId = uuid.v4(); try { @@ -36,7 +37,7 @@ module.exports.getProcessor = async function (processorId) { if (processor) { return processor; } else { - const error = generateError(ERROR_MESSAGES.NOT_FOUND, 404); + const error = generateError(404, ERROR_MESSAGES.NOT_FOUND); throw error; } }; @@ -46,7 +47,7 @@ module.exports.deleteProcessor = async function (processorId) { if (tests.length > 0) { let testNames = tests.map(test => test.name); let message = `${ERROR_MESSAGES.PROCESSOR_DELETION_FORBIDDEN}: ${testNames.join(', ')}`; - throw generateError(message, 409); + throw generateError(409, message); } return databaseConnector.deleteProcessor(processorId); }; @@ -54,12 +55,12 @@ module.exports.deleteProcessor = async function (processorId) { module.exports.updateProcessor = async function (processorId, processor) { const oldProcessor = await databaseConnector.getProcessorById(processorId); if (!oldProcessor) { - throw generateError(ERROR_MESSAGES.NOT_FOUND, 404); + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); } if (oldProcessor.name !== processor.name) { const processorWithUpdatedName = await databaseConnector.getProcessorByName(processor.name); if (processorWithUpdatedName) { - throw generateError(ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST, 400); + throw generateError(400, ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST); } } @@ -79,19 +80,13 @@ function verifyJSAndGetExportedFunctions(src) { let exports = m.exports; exportedFunctions = Object.keys(exports); } catch (err) { - let error = generateError('javascript syntax validation failed with error: ' + err.message, 422); + let error = generateError(422, 'javascript syntax validation failed with error: ' + err.message); throw error; } if (exportedFunctions.length === 0) { - let error = generateError('javascript has 0 exported functions', 422); + let error = generateError(422, 'javascript has 0 exported functions'); throw error; } return exportedFunctions; } - -function generateError(message, statusCode) { - const error = new Error(message); - error.statusCode = statusCode; - return error; -} diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index a957c8349..0def59d2d 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -40,4 +40,14 @@ module.exports.deleteWebhook = async function (req, res, next) { } catch (err) { return next(err); } +}; + +module.exports.updateWebhook = async function (req, res, next) { + let { body: updatedWebhook, params: { webhook_id: webhookId } } = req; + try { + const webhook = await webhookManager.updateWebhook(webhookId, updatedWebhook); + return res.status(200).json(webhook); + } catch (err) { + return next(err); + } }; \ No newline at end of file diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index 432a77fd3..844a2d71f 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -3,7 +3,8 @@ let logger = require('../../../../common/logger'); module.exports = { init, createWebhook, - getAllWebhooks + getAllWebhooks, + updateWebhook }; async function init() { @@ -19,3 +20,7 @@ async function getAllWebhooks() { async function createWebhook() { throw new Error('Not implemented.'); } + +async function updateWebhook() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index b63c53018..94f4f17b7 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -9,6 +9,7 @@ module.exports = { createWebhook, getWebhook, deleteWebhook, + updateWebhook, closeConnection }; @@ -34,4 +35,8 @@ async function getWebhook(webhookId) { async function deleteWebhook(webhookId) { return databaseConnector.deleteWebhook(webhookId); +}; + +async function updateWebhook(webhookId, webhook) { + return databaseConnector.updateWebhook(webhookId, webhook); }; \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index f526c9137..3ab471c20 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -8,6 +8,7 @@ module.exports = { getAllWebhooks, createWebhook, getWebhook, + updateWebhook, deleteWebhook }; @@ -18,6 +19,11 @@ function parseWebhook(webhookRecord) { }; }; +async function _getWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + return webhooksModel.findByPk(webhookId, { include: ['events'] }); +} + async function init(sequelizeClient) { client = sequelizeClient; await initSchemas(); @@ -30,8 +36,7 @@ async function getAllWebhooks() { } async function getWebhook(webhookId) { - const webhooksModel = client.model('webhook'); - const webhook = await webhooksModel.findByPk(webhookId, { include: ['events'] }); + const webhook = await _getWebhook(webhookId); return parseWebhook(webhook); } @@ -67,6 +72,21 @@ async function deleteWebhook(webhookId) { }); } +async function updateWebhook(webhookId, updatedWebhook) { + const webhooksModel = client.model('webhook'); + const webhooksEvents = client.model('webhook_event'); + + const oldWebhook = await _getWebhook(webhookId); + const newWebhookEvents = await webhooksEvents.findAll({ where: { name: updatedWebhook.events } }); + const newWebhookEventsIds = newWebhookEvents.map(({ id }) => id); + + await client.transaction(async function(transaction) { + await oldWebhook.setEvents(newWebhookEventsIds, { transaction }); + return webhooksModel.update(updatedWebhook, { where: { id: webhookId }, transaction }); + }); + return getWebhook(webhookId); +} + async function initSchemas() { const webhooksSchema = client.define('webhook', { id: { diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 2be6f9986..d40efad4c 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -2,27 +2,26 @@ const databaseConnector = require('./database/databaseConnector'); const { ERROR_MESSAGES } = require('../../common/consts'); +const generateError = require('../../common/generateError'); const webhookDefaultValues = { global: false }; -module.exports.getAllWebhooks = async function () { +async function getAllWebhooks() { let getAllWebhooks = await databaseConnector.getAllWebhooks(); return getAllWebhooks; }; -module.exports.getWebhook = async function (webhookId) { +async function getWebhook(webhookId) { const webhook = await databaseConnector.getWebhook(webhookId); if (!webhook) { - const error = new Error(ERROR_MESSAGES.NOT_FOUND); - error.statusCode = 404; - throw error; + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); } return webhook; }; -module.exports.createWebhook = async function(webhookInfo) { +async function createWebhook(webhookInfo) { const webhook = { ...webhookDefaultValues, ...webhookInfo @@ -30,6 +29,22 @@ module.exports.createWebhook = async function(webhookInfo) { return databaseConnector.createWebhook(webhook); }; -module.exports.deleteWebhook = async function(webhookId) { +async function deleteWebhook(webhookId) { return databaseConnector.deleteWebhook(webhookId); +}; + +async function updateWebhook(webhookId, webhook) { + const webhookInDB = await getWebhook(webhookId); + if (!webhookInDB) { + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); + } + return databaseConnector.updateWebhook(webhookId, webhook); +}; + +module.exports = { + getAllWebhooks, + getWebhook, + createWebhook, + deleteWebhook, + updateWebhook }; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 1e53ae499..098187c4c 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -10,5 +10,6 @@ router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); router.delete('/:webhook_id', swaggerValidator.validate, webhooksController.deleteWebhook); +router.put('/:webhook_id', swaggerValidator.validate, webhooksController.updateWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index ae3d53ba0..383421aea 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -10,7 +10,8 @@ module.exports = { createWebhook, getWebhooks, getWebhook, - deleteWebhook + deleteWebhook, + updateWebhook }; async function init() { @@ -57,4 +58,14 @@ function deleteWebhook(webhookId) { .expect(function (res) { return res; }); +} + +function updateWebhook(webhookId, webhook) { + return request(app) + .put(`${resourceUri}/${webhookId}`) + .send(webhook) + .set(headers) + .expect(function (res) { + return res; + }); } \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index f2df0f9f3..5e208637e 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -1,7 +1,7 @@ const { expect } = require('chai'); const uuid = require('uuid'); -const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); +const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPES, WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED } = require('../../../src/common/consts'); const webhookRequestSender = require('./helpers/requestCreator'); @@ -87,6 +87,95 @@ describe('Webhooks api', function () { expect(deleteWebhookResponse.statusCode).to.equal(204); }); }); + describe('PUT /v1/webhooks/:webhook_id', function() { + it('Insert a webhook -> update global -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, global: !webhook.global }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update url -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, url: `${webhook}/new/path` }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update format_type -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedFormatType = EVENT_FORMAT_TYPES.filter(format => format !== webhook.format_type)[0]; + const updatedWebhook = { ...webhook, format_type: updatedFormatType }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update name -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, name: webhook.name + ' added text' }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update events -> ensure it is updated', async function() { + const webhook = { + ...generateWebhook(), + events: [WEBHOOK_EVENT_TYPE_API_FAILURE] + }; + const updatedWebhook = { + ...webhook, + events: [WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED] + }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + }); }); describe('Bad requests', function () { @@ -162,6 +251,16 @@ describe('Webhooks api', function () { const response = await webhookRequestSender.deleteWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); + }); + }); + describe('PUT /v1/webhooks/:webhook_id', function() { + it('should return 400 for bad uuid', async function() { + const webhook = generateWebhook(); + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.updateWebhook(badWebhookValue, webhook); + expect(response.statusCode).to.equal(400); }); }); @@ -174,6 +273,16 @@ describe('Webhooks api', function () { const response = await webhookRequestSender.getWebhook(notExistingWebhookId); + expect(response.statusCode).to.equal(404); + }); + }); + describe('PUT /v1/webhooks/:webhook_id', function () { + it('should return 404 for no existing webhook', async function() { + const notExistingWebhookId = uuid.v4(); + const webhook = generateWebhook(); + + const response = await webhookRequestSender.updateWebhook(notExistingWebhookId, webhook); + expect(response.statusCode).to.equal(404); }); }); From 36a862e1ae4517cb80adb30ae1addc8f87ab440a Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sat, 25 Jul 2020 19:11:51 +0300 Subject: [PATCH 18/38] feat(webhooks): implement webhooks and jobs integrations --- .../database/cassandra/cassandraConnector.js | 7 +++- .../models/database/databaseConnector.js | 7 +++- .../database/sequelize/sequelizeConnector.js | 9 +++- src/webhooks/models/webhookManager.js | 41 ++++++++++++++++++- src/webhooks/models/webhooksFormatter.js | 14 +++++++ 5 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/webhooks/models/webhooksFormatter.js diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index 844a2d71f..d5334d324 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -4,7 +4,8 @@ module.exports = { init, createWebhook, getAllWebhooks, - updateWebhook + updateWebhook, + getAllGlobalWebhooks }; async function init() { @@ -24,3 +25,7 @@ async function createWebhook() { async function updateWebhook() { throw new Error('Not implemented.'); } + +async function getAllGlobalWebhooks() { + throw new Error('Not implemented.'); +} \ No newline at end of file diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 94f4f17b7..d0381acb0 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -10,6 +10,7 @@ module.exports = { getWebhook, deleteWebhook, updateWebhook, + getAllGlobalWebhooks, closeConnection }; @@ -39,4 +40,8 @@ async function deleteWebhook(webhookId) { async function updateWebhook(webhookId, webhook) { return databaseConnector.updateWebhook(webhookId, webhook); -}; \ No newline at end of file +}; + +async function getAllGlobalWebhooks() { + return databaseConnector.getAllGlobalWebhooks(); +} diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 3ab471c20..1563f2633 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -9,7 +9,8 @@ module.exports = { createWebhook, getWebhook, updateWebhook, - deleteWebhook + deleteWebhook, + getAllGlobalWebhooks }; function parseWebhook(webhookRecord) { @@ -40,6 +41,12 @@ async function getWebhook(webhookId) { return parseWebhook(webhook); } +async function getAllGlobalWebhooks() { + const webhooksModel = client.model('webhook'); + const webhooks = await webhooksModel.findAll({ include: ['events'], where: { global: true } }); + return webhooks.map(parseWebhook); +} + async function createWebhook(webhook) { const id = uuid.v4(); const webhooksModel = client.model('webhook'); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index d40efad4c..9ff5e0edd 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -3,6 +3,9 @@ const databaseConnector = require('./database/databaseConnector'); const { ERROR_MESSAGES } = require('../../common/consts'); const generateError = require('../../common/generateError'); +const requestSender = require('../../common/requestSender'); +const logger = require('../../common/logger'); +const webhooksFormatter = require('./webhooksFormatter'); const webhookDefaultValues = { global: false @@ -41,10 +44,46 @@ async function updateWebhook(webhookId, webhook) { return databaseConnector.updateWebhook(webhookId, webhook); }; +async function getAllGlobalWebhooks() { + return databaseConnector.getAllWebhooks(); +} + +async function fireSingleWebhook(webhook, payload) { + let webhookResponse = null; + try { + webhookResponse = await requestSender.send({ + method: 'POST', + url: webhook.url, + body: payload + }); + logger.info(`Webhook fired successfully, url=${webhook.url}`); + } catch (requestError) { + logger.error(`Webhook failed, url=${webhook.url}`); + throw requestError; + } + return webhookResponse; +} + +async function fireWebhooks(webhooks, payload) { + return webhooks.map(webhook => fireSingleWebhook(webhook, webhooksFormatter[webhook.format_type](payload))); +} + +async function fireWebhookByEvent(jobId, eventType, payload) { + const job = await getWebhook(jobId); + const globalWebhooks = await getAllGlobalWebhooks(); + const webhooks = [...job.webhooks, ...globalWebhooks]; + const webhooksWithEventType = webhooks.filter(webhook => webhook.events.include(eventType)); + if (webhooksWithEventType.length === 0) { + return; + } + await Promise.allSettled(fireWebhooks(webhooksWithEventType, payload)); +} + module.exports = { getAllWebhooks, getWebhook, createWebhook, deleteWebhook, - updateWebhook + updateWebhook, + fireWebhookByEvent }; \ No newline at end of file diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js new file mode 100644 index 000000000..b3b0f5801 --- /dev/null +++ b/src/webhooks/models/webhooksFormatter.js @@ -0,0 +1,14 @@ +const { EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK } = require('../../common/consts'); + +function json(payload) { + return payload; +} + +function slack(payload) { + // TODO: make it slacky +} + +module.exports = { + [EVENT_FORMAT_TYPE_JSON]: json, + [EVENT_FORMAT_TYPE_SLACK]: slack +}; From b19fa0bb44e9299e7e8e3dddf6cbfb88f82f2c0a Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sat, 8 Aug 2020 11:20:02 +0300 Subject: [PATCH 19/38] feat(webhooks): integrating the reports for webhooks --- .eslintrc.json | 2 +- src/common/consts.js | 9 +- src/reports/models/notifier.js | 145 +++++--------- .../models/statsFormatter.js | 0 src/webhooks/models/webhooksFormatter.js | 189 +++++++++++++++++- .../reporter/models/statsFormatter-test.js | 2 +- 6 files changed, 243 insertions(+), 104 deletions(-) rename src/{reports => webhooks}/models/statsFormatter.js (100%) diff --git a/.eslintrc.json b/.eslintrc.json index 4c0ddf4ef..01af938c7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,7 +13,7 @@ "rules": { "eol-last": "off", "space-before-function-paren": "off", - "indent": ["error", 4], + "indent": ["error", 4, { "SwitchCase": 1 }], "quotes": ["error", "single", { "avoidEscape": true }], "semi": ["error", "always",{ "omitLastInOneLineBlock": true}], "one-var": ["off"], diff --git a/src/common/consts.js b/src/common/consts.js index ebf0b2de2..beb5fe126 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -5,8 +5,11 @@ const WEBHOOK_EVENT_TYPE_FINISHED = 'finished'; const WEBHOOK_EVENT_TYPE_API_FAILURE = 'api_failure'; const WEBHOOK_EVENT_TYPE_ABORTED = 'aborted'; const WEBHOOK_EVENT_TYPE_FAILED = 'failed'; +const WEBHOOK_EVENT_TYPE_IN_PROGRESS = 'in_progress'; const WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED = 'benchmark_passed'; const WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED = 'benchmark_failed'; +const WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON = ':muscle:'; +const WEBHOOK_SLACK_DEFAULT_REPORTER_NAME = 'reporter'; module.exports = { TEST_TYPE_BASIC: 'basic', @@ -19,8 +22,11 @@ module.exports = { WEBHOOK_EVENT_TYPE_FAILED, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS, EVENT_FORMAT_TYPE_SLACK, EVENT_FORMAT_TYPE_JSON, + WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON, + WEBHOOK_SLACK_DEFAULT_REPORTER_NAME, EVENT_FORMAT_TYPES: [ EVENT_FORMAT_TYPE_SLACK, EVENT_FORMAT_TYPE_JSON @@ -32,7 +38,8 @@ module.exports = { WEBHOOK_EVENT_TYPE_ABORTED, WEBHOOK_EVENT_TYPE_FAILED, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, - WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS ], ERROR_MESSAGES: { NOT_FOUND: 'Not found', diff --git a/src/reports/models/notifier.js b/src/reports/models/notifier.js index 2b2670933..95f35ac17 100644 --- a/src/reports/models/notifier.js +++ b/src/reports/models/notifier.js @@ -3,14 +3,24 @@ const reportEmailSender = require('./reportEmailSender'), reportWebhookSender = require('./reportWebhookSender'), jobsManager = require('../../jobs/models/jobManager'), - statsFromatter = require('./statsFormatter'), + statsFromatter = require('../../webhooks/models/statsFormatter'), aggregateReportGenerator = require('./aggregateReportGenerator'), logger = require('../../common/logger'), constants = require('../utils/constants'), configHandler = require('../../configManager/models/configHandler'), reportUtil = require('../utils/reportUtil'), reportsManager = require('./reportsManager'), - configConstants = require('../../common/consts').CONFIG; + webhooksManager = require('../../webhooks/models/webhookManager'); +const { + CONFIG: configConstants, + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS +} = require('../../common/consts'); module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { let job; @@ -18,137 +28,86 @@ module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { try { job = await jobsManager.getJob(report.job_id); switch (stats.phase_status) { - case constants.SUBSCRIBER_FAILED_STAGE: - logger.info(metadata, stats.error, 'handling error message'); - await handleError(report, job, stats); - break; - case constants.SUBSCRIBER_STARTED_STAGE: - logger.info(metadata, 'handling started message'); - await handleStart(report, job); - break; - case constants.SUBSCRIBER_FIRST_INTERMEDIATE_STAGE: - logger.info(metadata, 'handling intermediate message'); - await handleFirstIntermediate(report, job); - break; - case constants.SUBSCRIBER_DONE_STAGE: - logger.info(metadata, 'handling done message'); - await handleDone(report, job, reportBenchmark); - break; - case constants.SUBSCRIBER_ABORTED_STAGE: - logger.info(metadata, 'handling aborted message'); - await handleAbort(report, job); - break; - default: - logger.trace(metadata, 'Handling unsupported test status: ' + JSON.stringify(stats)); - break; + case constants.SUBSCRIBER_FAILED_STAGE: { + logger.info(metadata, stats.error, 'handling error message'); + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FAILED, { report, stats }); + break; + } + case constants.SUBSCRIBER_STARTED_STAGE: { + logger.info(metadata, 'handling started message'); + await handleStart(report, job); + break; + } + case constants.SUBSCRIBER_FIRST_INTERMEDIATE_STAGE: { + logger.info(metadata, 'handling intermediate message'); + await handleFirstIntermediate(report, job); + break; + } + case constants.SUBSCRIBER_DONE_STAGE: { + logger.info(metadata, 'handling done message'); + await handleDone(report, job, reportBenchmark); + break; + } + case constants.SUBSCRIBER_ABORTED_STAGE: { + logger.info(metadata, 'handling aborted message'); + await handleAbort(report, job); + break; + } + default: { + logger.trace(metadata, 'Handling unsupported test status: ' + JSON.stringify(stats)); + break; + } } } catch (err) { logger.error(err, `Failed to notify for testId ${report.test_id} with reportID ${report.report_id}`); } }; -async function handleError(report, job, stats) { - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - - const webhookMessage = `😞 *Test with id: ${report.test_id} Failed*.\ntest configuration:\nenvironment: ${report.environment}\n${stats.data}`; - reportWebhookSender.send(webhooks, webhookMessage); -} - async function handleStart(report, job) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_STARTED_STAGE)) { return; } - - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - - let webhookMessage; - let rampToMessage = report.ramp_to ? `, ramp to: ${report.ramp_to} scenarios per second` : ''; - let parallelism = report.parallelism || 1; - webhookMessage = `🤓 *Test ${report.test_name} with id: ${report.test_id} has started*.\n - *test configuration:* environment: ${report.environment} duration: ${report.duration} seconds, arrival rate: ${report.arrival_rate} scenarios per second, number of runners: ${parallelism}${rampToMessage}`; - - reportWebhookSender.send(webhooks, webhookMessage); + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_STARTED, { report }); } async function handleFirstIntermediate(report, job) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_FIRST_INTERMEDIATE_STAGE)) { return; } - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - - let webhookMessage; + // WHAT DO WE DO WITH BATCHES let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - const phaseIndex = report.phase; - webhookMessage = `🤔 *Test ${report.test_name} with id: ${report.test_id} first batch of results arrived for phase ${phaseIndex}.*\n${statsFromatter.getStatsFormatted('intermediate', aggregatedReport.aggregate)}\n`; - if (report.grafana_report) { - webhookMessage += `<${report.grafana_report}|Track report in grafana dashboard>`; - } - reportWebhookSender.send(webhooks, webhookMessage); + webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_IN_PROGRESS, { report, aggregatedReport }); } async function handleDone(report, job, reportBenchmark) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_DONE_STAGE)) { return; } - let emails = await getEmailTargets(job); - let webhooks = await getWebhookTargets(job); const { benchmarkThreshold, benchmarkWebhook } = await getBenchmarkConfig(); - if (emails.length === 0 && webhooks.length === 0 && benchmarkWebhook.length === 0) { + if (emails.length === 0 && benchmarkWebhook.length === 0) { return; } let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - let webhookMessage = `😎 *Test ${report.test_name} with id: ${report.test_id} is finished.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; - - if (report.grafana_report) { - webhookMessage += `<${report.grafana_report}|View final grafana dashboard report>`; - } if (emails.length > 0) { reportEmailSender.sendAggregateReport(aggregatedReport, job, emails, reportBenchmark); } - if (webhooks.length > 0) { - reportWebhookSender.send(webhooks, webhookMessage); - } + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FINISHED, { report, aggregatedReport, reportBenchmark }); - if (benchmarkWebhook.length > 0) { - handleBenchmarkWebhookTreshhold(aggregatedReport, reportBenchmark.score, benchmarkThreshold, benchmarkWebhook); + if (reportBenchmark.score && benchmarkThreshold) { + const lastReports = await reportsManager.getReports(aggregatedReport.test_id); + const lastScores = lastReports.slice(0, 3).filter(report => report.score).map(report => report.score.toFixed(1)); + let event = reportBenchmark.score < benchmarkThreshold ? WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED : WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED; + await webhooksManager.fireWebhookByEvent(job.id, event, { aggregatedReport, score: reportBenchmark.score, lastScores, icon: ':sad_1:' }); } } async function handleAbort(report, job) { - let webhooks = await getWebhookTargets(job); - if (webhooks.length === 0) { - return; - } - let webhookMessage = `😢 *Test ${report.test_name} with id: ${report.test_id} was aborted.*\n`; - if (report.grafana_report) { - webhookMessage += `<${report.grafana_report}|View final grafana dashboard report>`; - } - reportWebhookSender.send(webhooks, webhookMessage); -} - -async function handleBenchmarkWebhookTreshhold(aggregatedReport, score, benchmarkThreshold, benchmarkWebhook) { - if (score && benchmarkThreshold && score < benchmarkThreshold) { - const lastReports = await reportsManager.getReports(aggregatedReport.test_id); - const lastScores = lastReports.slice(0, 3).filter(report => report.score).map(report => report.score.toFixed(1)); - let benchmarkWebhookMsg = `:sad_1: *Test ${aggregatedReport.test_name} got a score of ${score.toFixed(1)}` + - ` this is below the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + - `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; - reportWebhookSender.send([benchmarkWebhook], benchmarkWebhookMsg, { icon: ':sad_1:' }); - } + await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_ABORTED, { report }); } async function getWebhookTargets(job) { diff --git a/src/reports/models/statsFormatter.js b/src/webhooks/models/statsFormatter.js similarity index 100% rename from src/reports/models/statsFormatter.js rename to src/webhooks/models/statsFormatter.js diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index b3b0f5801..237f6972d 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -1,14 +1,187 @@ -const { EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK } = require('../../common/consts'); +const { + EVENT_FORMAT_TYPE_JSON, + EVENT_FORMAT_TYPE_SLACK, + WEBHOOK_EVENT_TYPES, + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON, + WEBHOOK_SLACK_DEFAULT_REPORTER_NAME, + WEBHOOK_EVENT_TYPE_IN_PROGRESS +} = require('../../common/consts'); +const statsFromatter = require('./statsFormatter'); -function json(payload) { +function unknownWebhookEventTypeError(badWebhookEventTypeValue) { + return new Error(`Unrecognized webhook event: ${badWebhookEventTypeValue}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`); +} + +function slackWebhookFormat(message, options) { + return { + text: message, + icon_emoji: options.icon || WEBHOOK_SLACK_DEFAULT_MESSAGE_ICON, + username: WEBHOOK_SLACK_DEFAULT_REPORTER_NAME + }; +} + +function json(event, testId, jobId, report, options) { + let payload = { + test_id: testId, + job_id: jobId, + event: event, + additional_details: {} + }; + let additionalDetails = {}; + switch (event) { + case WEBHOOK_EVENT_TYPE_STARTED: { + additionalDetails = { + test_name: , + environment: , + duration: , + arrival_rate: , + parallelism: , + ramp_to: rampTo, + }; + break; + } + case WEBHOOK_EVENT_TYPE_FINISHED: { + additionalDetails = { + test_name: , + grafana_report: , + aggregated_report: { + ...aggregatedReport.aggregate + }, + report_benchmark: { + ...reportBenchmark + } + }; + break; + } + case WEBHOOK_EVENT_TYPE_FAILED: { + additionalDetails = { + environment: , + stats: { + ...stats.data + } + }; + break; + } + case WEBHOOK_EVENT_TYPE_ABORTED: { + additionalDetails = { + testName, + grafanaReport: + }; + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { + additionalDetails = { + aggregated_test_name: , + benchmark_threshold: , + score: , + last_three_scores: , + aggregated_report: + }; + break; + } + case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + additionalDetails = { + + }; + break; + } + case WEBHOOK_EVENT_TYPE_API_FAILURE: { + additionalDetails = { + + }; + break; + } + default: { + throw unknownWebhookEventTypeError(); + } + } + payload.additional_details = additionalDetails; return payload; } -function slack(payload) { - // TODO: make it slacky +function slack(event, testId, jobId, report, options) { + let message = null; + const { + environment, + duration, + parallelism = 1, + ramp_to: rampTo, + arrival_rate: arrivalRate, + test_name: testName, + grafana_report: grafanaReport + } = report; + const { aggregatedReport, reportBenchmark } = options; + switch (event) { + case WEBHOOK_EVENT_TYPE_STARTED: { + let rampToMessage = rampTo ? `, ramp to: ${rampTo} scenarios per second` : ''; + message = `🤓 *Test ${testName} with id: ${testId} has started*.\n + *test configuration:* environment: ${environment} duration: ${duration} seconds, arrival rate: ${arrivalRate} scenarios per second, number of runners: ${parallelism}${rampToMessage}`; + break; + } + case WEBHOOK_EVENT_TYPE_FINISHED: { + message = `😎 *Test ${testName} with id: ${testId} is finished.*\n + ${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; + if (grafanaReport) { + message += `<${grafanaReport}|View final grafana dashboard report>`; + } + break; + } + case WEBHOOK_EVENT_TYPE_FAILED: { + message = `😞 *Test with id: ${testId} Failed*.\n + test configuration:\n + environment: ${environment}\n + ${stats.data}`; + break; + } + case WEBHOOK_EVENT_TYPE_ABORTED: { + message = `😢 *Test ${testName} with id: ${testId} was aborted.*\n`; + if (grafanaReport) { + message += `<${grafanaReport}|View final grafana dashboard report>`; + } + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { + message = `:sad_1: *Test ${aggregatedtestName} got a score of ${score.toFixed(1)}` + + ` this is below the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + + `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; + break; + } + case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { + break; + } + case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + break; + } + case WEBHOOK_EVENT_TYPE_API_FAILURE: { + break; + } + default: { + throw unknownWebhookEventTypeError(); + } + } + return slackWebhookFormat(message); } -module.exports = { - [EVENT_FORMAT_TYPE_JSON]: json, - [EVENT_FORMAT_TYPE_SLACK]: slack -}; +module.exports = function(format, eventType, jobId, testId, report, options={}) { + switch(format) { + case EVENT_FORMAT_TYPE_SLACK: { + return slack(eventType, testId, jobId, report, options); + } + case EVENT_FORMAT_TYPE_JSON: { + return json(eventType, testId, jobId, report, options); + } + default: { + throw new Error(`Unrecognized webhook format: ${format}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`) + } + } +} \ No newline at end of file diff --git a/tests/unit-tests/reporter/models/statsFormatter-test.js b/tests/unit-tests/reporter/models/statsFormatter-test.js index 2e136da92..0e2e532f3 100644 --- a/tests/unit-tests/reporter/models/statsFormatter-test.js +++ b/tests/unit-tests/reporter/models/statsFormatter-test.js @@ -1,7 +1,7 @@ 'use strict'; let sinon = require('sinon'); let should = require('should'); -let statsFormatter = require('../../../../src/reports/models/statsFormatter'); +let statsFormatter = require('../../../../src/webhooks/models/statsFormatter'); const REPORT = { 'timestamp': '2018-05-15T14:20:02.109Z', From 086ac7c2a2b83cf2ff039a6015b5c440daf6e34e Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 9 Aug 2020 23:43:52 +0300 Subject: [PATCH 20/38] chore(dependencies): npm audit fix --- package-lock.json | 1131 +++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 586 insertions(+), 547 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49f7f648a..62b20d11b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -154,20 +154,12 @@ "dev": true }, "@babel/runtime": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", - "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - } } }, "@babel/template": { @@ -227,26 +219,70 @@ } }, "@commitlint/cli": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-9.0.1.tgz", - "integrity": "sha512-BVOc/BY0FMmKTTH5oUVE0ukhPWDFf364FiYKk3GlXLOGTZPTXQ/9ncB2eMOaCF0PdcEVY4VoMjyoRSgcVapCMg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-9.1.2.tgz", + "integrity": "sha512-ctRrrPqjZ8r4Vc4FXpPaScEpkPwfvB0Us3NK2SD2AnLwXGMxOLFTabDmNySU1Xc40ud2CmJsaV8lpavvzs8ZZA==", "dev": true, "requires": { "@babel/runtime": "^7.9.6", - "@commitlint/format": "^9.0.1", - "@commitlint/lint": "^9.0.1", - "@commitlint/load": "^9.0.1", - "@commitlint/read": "^9.0.1", - "chalk": "3.0.0", + "@commitlint/format": "^9.1.2", + "@commitlint/lint": "^9.1.2", + "@commitlint/load": "^9.1.2", + "@commitlint/read": "^9.1.2", + "chalk": "4.1.0", "core-js": "^3.6.1", "get-stdin": "7.0.0", - "lodash": "^4.17.15", - "meow": "5.0.0", - "regenerator-runtime": "0.13.3", + "lodash": "^4.17.19", "resolve-from": "5.0.0", - "resolve-global": "1.0.0" + "resolve-global": "1.0.0", + "yargs": "^15.1.0" }, "dependencies": { + "@commitlint/execute-rule": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-9.1.2.tgz", + "integrity": "sha512-NGbeo0KCVYo1yj9vVPFHv6RGFpIF6wcQxpFYUKGIzZVV9Vz1WyiKS689JXa99Dt1aN0cZlEJJLnTNDIgYls0Vg==", + "dev": true + }, + "@commitlint/load": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-9.1.2.tgz", + "integrity": "sha512-FPL82xBuF7J3EJ57kLVoligQP4BFRwrknooP+vNT787AXmQ/Fddc/iYYwHwy67pNkk5N++/51UyDl/CqiHb6nA==", + "dev": true, + "requires": { + "@commitlint/execute-rule": "^9.1.2", + "@commitlint/resolve-extends": "^9.1.2", + "@commitlint/types": "^9.1.2", + "chalk": "4.1.0", + "cosmiconfig": "^6.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0" + } + }, + "@commitlint/resolve-extends": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-9.1.2.tgz", + "integrity": "sha512-HcoL+qFGmWEu9VM4fY0HI+VzF4yHcg3x+9Hx6pYFZ+r2wLbnKs964y0v68oyMO/mS/46MVoLNXZGR8U3adpadg==", + "dev": true, + "requires": { + "import-fresh": "^3.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + } + }, + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + }, + "ansi-regex": { + "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": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -257,16 +293,33 @@ "color-convert": "^2.0.1" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "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" } }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -288,12 +341,109 @@ "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", "dev": true }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "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 }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parse-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", + "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "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": "^5.0.0" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -302,6 +452,46 @@ "requires": { "has-flag": "^4.0.0" } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -315,30 +505,46 @@ } }, "@commitlint/ensure": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-9.0.1.tgz", - "integrity": "sha512-z8SEkfbn0lMnAtt7Hp3A8hE3CRCDsg+Eu3Xj1UJakOyCPJgHE1/vEyM2DO2dxTXVKuttiHeLDnUSHCxklm78Ng==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-9.1.2.tgz", + "integrity": "sha512-hwQICwpNSTsZgj/1/SdPvYAzhwjwgCJI4vLbT879+Jc+AJ6sj2bUDGw/F89vzgKz1VnaMm4D65bNhoWhG3pdhQ==", "dev": true, "requires": { - "@commitlint/types": "^9.0.1", - "lodash": "^4.17.15" + "@commitlint/types": "^9.1.2", + "lodash": "^4.17.19" + }, + "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + } } }, "@commitlint/execute-rule": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-9.0.1.tgz", "integrity": "sha512-fxnLadXs59qOBE9dInfQjQ4DmbGToQ0NjfqqmN6N8qS+KsCecO6N0mMUrC95et9xTeimFRr+0l9UMfmRVHNS/w==", - "dev": true + "dev": true, + "optional": true }, "@commitlint/format": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-9.0.1.tgz", - "integrity": "sha512-5oY7Jyve7Bfnx0CdbxFcpRKq92vUANFq3MVbz/ZTgvuYgUeMuYsSEwW6MJtOgOhHBQ2vZP/uPdxwmU+6pWZHcg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-9.1.2.tgz", + "integrity": "sha512-+ZWTOSGEU6dbn3NRh1q7sY5K5QLiSs7E2uSzuYnWHXcQk8nlTvnE0ibwMCQxdKLaOTZiN57fHM/7M9Re2gsRuw==", "dev": true, "requires": { - "chalk": "^3.0.0" + "@commitlint/types": "^9.1.2", + "chalk": "^4.0.0" }, "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -350,9 +556,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "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", @@ -392,33 +598,47 @@ } }, "@commitlint/is-ignored": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-9.0.1.tgz", - "integrity": "sha512-doGBfQgbsi48Hc48runGdN0TQFvf5XZizck8cylQdGG/3w+YwX9WkplEor7cvz8pmmuD6PpfpdukHSKlR8KmHQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-9.1.2.tgz", + "integrity": "sha512-423W/+Ro+Cc8cg81+t9gds1EscMZNjnGT31nKDvxVxJxXiXQsYYoFEQbU+nfUrRGQsUikEgEJ3ppVGr1linvcQ==", "dev": true, "requires": { - "@commitlint/types": "^9.0.1", - "semver": "7.1.3" + "@commitlint/types": "^9.1.2", + "semver": "7.3.2" }, "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + }, "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } }, "@commitlint/lint": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-9.0.1.tgz", - "integrity": "sha512-EAn4E6aGWZ96Dg9LN28kdELqkyFOAUGlXWmanMdWxGFGdOf24ZHzlVsbr/Yb1oSBUE2KVvAF5W2Mzn2+Ge5rOg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-9.1.2.tgz", + "integrity": "sha512-XvggqHZ4XSTKOgzJhCzz52cWRRO57QQnEviwGj0qnD4jdwC+8h2u9LNZwoa2tGAuaNM3nSm//wNK7FRZhgiiFA==", "dev": true, "requires": { - "@commitlint/is-ignored": "^9.0.1", - "@commitlint/parse": "^9.0.1", - "@commitlint/rules": "^9.0.1", - "@commitlint/types": "^9.0.1" + "@commitlint/is-ignored": "^9.1.2", + "@commitlint/parse": "^9.1.2", + "@commitlint/rules": "^9.1.2", + "@commitlint/types": "^9.1.2" + }, + "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + } } }, "@commitlint/load": { @@ -426,6 +646,7 @@ "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-9.0.1.tgz", "integrity": "sha512-6ix/pUjVAggmDLTcnpyk0bgY3H9UBBTsEeFvTkHV+WQ6LNIxsQk8SwEOEZzWHUqt0pxqMQeiUgYeSZsSw2+uiw==", "dev": true, + "optional": true, "requires": { "@commitlint/execute-rule": "^9.0.1", "@commitlint/resolve-extends": "^9.0.1", @@ -441,6 +662,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, + "optional": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -451,6 +673,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, + "optional": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -461,6 +684,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "optional": true, "requires": { "color-name": "~1.1.4" } @@ -469,13 +693,15 @@ "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 + "dev": true, + "optional": true }, "cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, + "optional": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.1.0", @@ -488,13 +714,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "dev": true, + "optional": true }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, + "optional": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -506,13 +734,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "dev": true, + "optional": true }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, + "optional": true, "requires": { "has-flag": "^4.0.0" } @@ -520,15 +750,15 @@ } }, "@commitlint/message": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-9.0.1.tgz", - "integrity": "sha512-9rKnOeBV5s5hnV895aE3aMgciC27kAjkV9BYVQOWRjZdXHFZxa+OZ94mkMp+Hcr61W++fox1JJpPiTuCTDX3TQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-9.1.2.tgz", + "integrity": "sha512-ndlx5z7bPVLG347oYJUHuQ41eTcsw+aUYT1ZwQyci0Duy2atpuoeeSw9SuM1PjufzRCpb6ExzFEgGzcCRKAJsg==", "dev": true }, "@commitlint/parse": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-9.0.1.tgz", - "integrity": "sha512-O39yMSMFdBtqwyM5Ld7RT6OGeI7jiXB9UUb09liIXIkltaZZo6CeoBD9hyfRWpaw81SiGL4OwHzp92mYVHLmow==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-9.1.2.tgz", + "integrity": "sha512-d+/VYbkotctW+lzDpus/R6xTerOqFQkW1myH+3PwnqYSE6JU/uHT4MlZNGJBv8pX9SPlR66t6X9puFobqtezEw==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", @@ -536,12 +766,12 @@ } }, "@commitlint/read": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-9.0.1.tgz", - "integrity": "sha512-EYbel85mAiHb56bS5jBJ71lEaGjTnkSJLxTV1u6dpxdSBkRdmAn2DSPd6KQSbwYGUlPCR+pAZeZItT1y0Xk3hg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-9.1.2.tgz", + "integrity": "sha512-C2sNBQOqeQXMxpWtRnXYKYB3D9yuybPtQNY/P67A6o8XH/UMHkFaUTyIx1KRgu0IG0yTTItRt46FGnsMWLotvA==", "dev": true, "requires": { - "@commitlint/top-level": "^9.0.1", + "@commitlint/top-level": "^9.1.2", "fs-extra": "^8.1.0", "git-raw-commits": "^2.0.0" }, @@ -564,6 +794,7 @@ "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-9.0.1.tgz", "integrity": "sha512-o6Lya2ILg1tEfWatS5x8w4ImvDzwb1whxsr2c/cxVCFqLF4hxHHHniZ0NJ+HFhYa1kBsYeKlD1qn9fHX5Y1+PQ==", "dev": true, + "optional": true, "requires": { "import-fresh": "^3.0.0", "lodash": "^4.17.15", @@ -572,27 +803,35 @@ } }, "@commitlint/rules": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-9.0.1.tgz", - "integrity": "sha512-K9IiQzF/C2tP/0mQUPSkOtmAEUleRQhZK1NFLVbsd6r4uobaczjPSYvEH+cuSHlD9b3Ori7PRiTgVBAZTH5ORQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-9.1.2.tgz", + "integrity": "sha512-1vecFuzqVqjiT57ocXq1bL8V6GEF1NZs3BR0dQzObaqHftImIxBVII299gasckTkcuxNc8M+7XxZyKxUthukpQ==", "dev": true, "requires": { - "@commitlint/ensure": "^9.0.1", - "@commitlint/message": "^9.0.1", - "@commitlint/to-lines": "^9.0.1", - "@commitlint/types": "^9.0.1" + "@commitlint/ensure": "^9.1.2", + "@commitlint/message": "^9.1.2", + "@commitlint/to-lines": "^9.1.2", + "@commitlint/types": "^9.1.2" + }, + "dependencies": { + "@commitlint/types": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.1.2.tgz", + "integrity": "sha512-r3fwVbVH+M8W0qYlBBZFsUwKe6NT5qvz+EmU7sr8VeN1cQ63z+3cfXyTo7WGGEMEgKiT0jboNAK3b1FZp8k9LQ==", + "dev": true + } } }, "@commitlint/to-lines": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-9.0.1.tgz", - "integrity": "sha512-FHiXPhFgGnvekF4rhyl1daHimEHkr81pxbHAmWG/0SOCehFr5THsWGoUYNNBMF7rdwUuVq4tXJpEOFiWBGKigg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-9.1.2.tgz", + "integrity": "sha512-o4zWcMf9EnzA3MOqx01780SgrKq5hqDJmUBPk30g6an0XcDuDy3OSZHHTJFdzsg4V9FjC4OY44sFeK7GN7NaxQ==", "dev": true }, "@commitlint/top-level": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-9.0.1.tgz", - "integrity": "sha512-AjCah5y7wu9F/hOwMnqsujPRWlKerX79ZGf+UfBpOdAh+USdV7a/UfQaqjgCzkxy5GcNO9ER5A+2mWrUHxJ0hQ==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-9.1.2.tgz", + "integrity": "sha512-KMPP5xVePcz3B1dKqcZdU4FZBVOkT+bG3ip4RQX2TeCJoomMkTjd0utALs7rpTGLID6BXbwwXepZCZJREjR/Bw==", "dev": true, "requires": { "find-up": "^4.0.0" @@ -638,7 +877,8 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.0.1.tgz", "integrity": "sha512-wo2rHprtDzTHf4tiSxavktJ52ntiwmg7eHNGFLH38G1of8OfGVwOc1sVbpM4jN/HK/rCMhYOi6xzoPqsv0537A==", - "dev": true + "dev": true, + "optional": true }, "@js-joda/core": { "version": "2.0.0", @@ -2394,26 +2634,80 @@ }, "dependencies": { "conventional-changelog-conventionalcommits": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.0.tgz", - "integrity": "sha512-oYHydvZKU+bS8LnGqTMlNrrd7769EsuEHKy4fh1oMdvvDi7fem8U+nvfresJ1IDB8K00Mn4LpiA/lR+7Gs6rgg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.1.tgz", + "integrity": "sha512-EQa7TJzF7H4EMkfjjJV7d+gragejDqa8NirZnCfRpruCMZqRbAJ8DqmYbkHrYtBYicXqgfM0zkk6HlvLPcyOdQ==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "lodash": "^4.17.15", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + } } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true } } }, "conventional-changelog-angular": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", - "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz", + "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + } } }, "conventional-changelog-atom": { @@ -2452,13 +2746,13 @@ } }, "conventional-changelog-core": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.1.7.tgz", - "integrity": "sha512-UBvSrQR2RdKbSQKh7RhueiiY4ZAIOW3+CSWdtKOwRv+KxIMNFKm1rOcGBFx0eA8AKhGkkmmacoTWJTqyz7Q0VA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.1.8.tgz", + "integrity": "sha512-M7VWA/RiVyjXIVt3SdfbgZFh0se67WBl78EzIYlBlFmDszzb00BwHlaNpgR1XrN0v56vtfBVq1sKEwQo2HbmkA==", "dev": true, "requires": { "add-stream": "^1.0.0", - "conventional-changelog-writer": "^4.0.16", + "conventional-changelog-writer": "^4.0.17", "conventional-commits-parser": "^3.1.0", "dateformat": "^3.0.0", "get-pkg-repo": "^1.0.0", @@ -2491,6 +2785,43 @@ "quick-lru": "^1.0.0" } }, + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "conventional-changelog-writer": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz", + "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.6", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^7.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^3.0.0" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -2513,6 +2844,23 @@ "through2": "^2.0.0" }, "dependencies": { + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -2531,6 +2879,12 @@ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", "dev": true }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2553,23 +2907,6 @@ "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", "dev": true }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" - } - }, "minimist-options": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", @@ -2651,6 +2988,12 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2711,92 +3054,48 @@ } }, "conventional-changelog-jshint": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.7.tgz", - "integrity": "sha512-qHA8rmwUnLiIxANJbz650+NVzqDIwNtc0TcpIa0+uekbmKHttidvQ1dGximU3vEDdoJVKFgR3TXFqYuZmYy9ZQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.8.tgz", + "integrity": "sha512-hB/iI0IiZwnZ+seYI+qEQ4b+EMQSEC8jGIvhO2Vpz1E5p8FgLz75OX8oB1xJWl+s4xBMB6f8zJr0tC/BL7YOjw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" - } - }, - "conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", - "dev": true - }, - "conventional-changelog-writer": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", - "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", - "dev": true, - "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.6", - "dateformat": "^3.0.0", - "handlebars": "^4.7.6", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^7.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, - "meow": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", - "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "requires": { - "@types/minimist": "^1.2.0", - "arrify": "^2.0.1", - "camelcase": "^6.0.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } + "is-obj": "^2.0.0" } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true } } }, + "conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", + "dev": true + }, "conventional-commit-types": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", @@ -4242,22 +4541,22 @@ } }, "eslint": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz", - "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.1.0", - "eslint-utils": "^2.0.0", - "eslint-visitor-keys": "^1.2.0", - "espree": "^7.1.0", - "esquery": "^1.2.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", @@ -4270,102 +4569,77 @@ "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", + "levn": "^0.3.0", "lodash": "^4.17.14", "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.8.3", "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "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==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "color-convert": "^1.9.0" } }, "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" - } - }, - "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==", + "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": { - "color-name": "~1.1.4" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "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 - }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "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" - } - }, - "eslint-utils": { - "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.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", - "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", - "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 + "dev": true, + "requires": { + "ms": "^2.1.1" + } }, "is-extglob": { "version": "2.1.1", @@ -4382,109 +4656,34 @@ "is-extglob": "^2.1.1" } }, - "levn": { - "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.2.1", - "type-check": "~0.4.0" - } - }, - "optionator": { - "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.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "shebang-command": { - "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": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "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": "^5.0.0" + "ansi-regex": "^4.1.0" } }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "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": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-check": { - "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.2.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "isexe": "^2.0.0" + "has-flag": "^3.0.0" } } } @@ -4729,22 +4928,14 @@ "dev": true }, "espree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", - "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^7.2.0", + "acorn": "^7.1.1", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.2.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", - "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", - "dev": true - } + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { @@ -5802,62 +5993,11 @@ "through2": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, "dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true - }, - "meow": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", - "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "arrify": "^2.0.1", - "camelcase": "^6.0.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - } - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } } } }, @@ -7636,149 +7776,48 @@ "integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo=" }, "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" }, "dependencies": { - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" - } - }, - "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" - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "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" - } - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", - "dev": true - }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.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 - }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", - "dev": true - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "dev": true, - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - } - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true - }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", "dev": true }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } } } } @@ -9715,9 +9754,9 @@ } }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, "regex-not": { diff --git a/package.json b/package.json index 599448f43..096750430 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "uuid": "^3.4.0" }, "devDependencies": { - "@commitlint/cli": "^9.0.1", + "@commitlint/cli": "^9.1.2", "@commitlint/config-conventional": "^9.0.1", "chai": "^4.2.0", "commitlint": "^9.0.1", From a0b4189154c0f96a559c6646c878e5dd60ff0b9e Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 10 Aug 2020 19:45:57 +0300 Subject: [PATCH 21/38] feat(webhooks): reports fire webhooks by event --- docs/openapi3.yaml | 12 +- package.json | 1 + src/common/consts.js | 1 + .../database/sequelize/sequelizeConnector.js | 31 +- src/jobs/models/jobManager.js | 46 +- src/reports/models/notifier.js | 41 +- src/reports/models/reportWebhookSender.js | 30 -- .../database/sequelize/sequelizeConnector.js | 1 + src/webhooks/models/webhookManager.js | 32 +- src/webhooks/models/webhooksFormatter.js | 207 +++++---- tests/testExamples/Basic_test.json | 2 +- .../unit-tests/jobs/models/jobManager-test.js | 5 +- .../jobs/sequelize/sequelizeConnector-test.js | 426 ++++++++++-------- 13 files changed, 429 insertions(+), 406 deletions(-) delete mode 100644 src/reports/models/reportWebhookSender.js diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 7beb2b003..4d16564b8 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -598,6 +598,12 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + '422': + description: Unprocessable entity + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' '500': description: Internal server error content: @@ -2108,6 +2114,7 @@ components: example: 0 0 12 * * ? webhooks: type: array + uniqueItems: true description: | An array of webhooks ids, events will be fired to the coresponding webhooks according to their events configuration. The event body will include detailed information about the test, such as the number of scenarios that were executed @@ -2587,10 +2594,11 @@ components: type: string enum: - started + - finished + - in_progress - api_failure - - aborted - failed - - finished + - aborted - benchmark_passed - benchmark_failed webhook_format_types: diff --git a/package.json b/package.json index 096750430..c571cbd62 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/package", "name": "predator", "version": "1.4.0", "description": "Framework that manages the entire lifecycle of load testing a server, from creating test files, running scheduled and on-demand tests, and viewing test results.", diff --git a/src/common/consts.js b/src/common/consts.js index beb5fe126..614ea006a 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -2,6 +2,7 @@ const EVENT_FORMAT_TYPE_SLACK = 'slack'; const EVENT_FORMAT_TYPE_JSON = 'json'; const WEBHOOK_EVENT_TYPE_STARTED = 'started'; const WEBHOOK_EVENT_TYPE_FINISHED = 'finished'; +// TODO: how to recognize api_failure? const WEBHOOK_EVENT_TYPE_API_FAILURE = 'api_failure'; const WEBHOOK_EVENT_TYPE_ABORTED = 'aborted'; const WEBHOOK_EVENT_TYPE_FAILED = 'failed'; diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index 7ac38a393..a490a0593 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -34,22 +34,21 @@ async function insertJob(jobId, jobInfo) { proxy_url: jobInfo.proxy_url, enabled: jobInfo.enabled, debug: jobInfo.debug, - webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { // still missing data attributes(name, global, format_type) - return { id: uuid(), url: webhookUrl }; - }) : undefined, emails: jobInfo.emails ? jobInfo.emails.map(emailAddress => { return { id: uuid(), address: emailAddress }; }) : undefined }; let include = []; - if (params.webhooks) { - include.push({ association: job.webhook }); - } if (params.emails) { include.push({ association: job.email }); } - return job.create(params, { include }); + let createdJob = null; + await client.transaction(async function(transaction) { + createdJob = await job.create(params, { include, transaction }); + return createdJob.setWebhooks(jobInfo.webhooks || [], { transaction }); + }); + return createdJob; } async function getJobsAndParse(jobId) { @@ -68,7 +67,7 @@ async function getJobsAndParse(jobId) { allJobs.forEach(job => { job.emails = job.emails && job.emails.length > 0 ? job.emails.map(sqlJob => sqlJob.dataValues.address) : undefined; - job.webhooks = job.webhooks && job.webhooks.length > 0 ? job.webhooks.map(sqlJob => sqlJob.dataValues.url) : undefined; + job.webhooks = job.webhooks && job.webhooks.length > 0 ? job.webhooks.map(sqlJob => sqlJob.dataValues.id) : undefined; }); return allJobs; } @@ -105,9 +104,14 @@ async function updateJob(jobId, jobInfo) { id: jobId } }; - - let result = await job.update(params, options); - return result; + let updatedJob = null; + let oldJob = await job.findByPk(jobId); + await client.transaction(async function(transaction) { + updatedJob = await job.update(params, { ...options, transaction }); + await oldJob.setWebhooks(jobInfo.webhooks || [], { transaction }); + return updatedJob; + }); + return updatedJob; } async function deleteJob(jobId) { @@ -179,12 +183,11 @@ async function initSchemas() { webhooks.belongsToMany(job, { through: 'webhook_job_mapping', as: 'jobs', - foreignKey: 'webhook_id', - onDelete: 'CASCADE' + foreignKey: 'webhook_id' }); job.belongsToMany(webhooks, { through: 'webhook_job_mapping', as: 'webhooks', foreignKey: 'job_id' }); -} \ No newline at end of file +} diff --git a/src/jobs/models/jobManager.js b/src/jobs/models/jobManager.js index d4250bdef..154067987 100644 --- a/src/jobs/models/jobManager.js +++ b/src/jobs/models/jobManager.js @@ -137,26 +137,32 @@ module.exports.getJob = async (jobId) => { module.exports.updateJob = async (jobId, jobConfig) => { const configData = await configHandler.getConfig(); - return databaseConnector.updateJob(jobId, jobConfig) - .then(function () { - return databaseConnector.getJob(jobId) - .then(function (updatedJob) { - if (updatedJob.length === 0) { - let error = new Error('Not found'); - error.statusCode = 404; - throw error; - } - if (cronJobs[jobId]) { - cronJobs[jobId].stop(); - delete cronJobs[jobId]; - } - addCron(jobId, updatedJob[0], updatedJob[0].cron_expression, configData); - logger.info('Job updated successfully to database'); - }); - }).catch(function (err) { - logger.error(err, 'Error occurred trying to update job'); - return Promise.reject(err); - }); + let job = null; + let updatedJob = null; + [ job ] = await databaseConnector.getJob(jobId); + if (!job.cron_expression) { + let error = new Error('Can not update jobs from type run_immediately: true'); + error.statusCode = 422; + throw error; + } + try { + updatedJob = await databaseConnector.updateJob(jobId, jobConfig); + job = await databaseConnector.getJob(jobId); + } catch (err) { + logger.error(err, 'Error occurred trying to update job'); + throw err; + } + if (job.length === 0) { + let error = new Error('Not found'); + error.statusCode = 404; + throw error; + } + if (cronJobs[jobId]) { + cronJobs[jobId].stop(); + delete cronJobs[jobId]; + } + addCron(jobId, job[0], job[0].cron_expression, configData); + logger.info('Job updated successfully to database'); }; function createResponse(jobId, jobBody, runId) { diff --git a/src/reports/models/notifier.js b/src/reports/models/notifier.js index 95f35ac17..d5b0723f7 100644 --- a/src/reports/models/notifier.js +++ b/src/reports/models/notifier.js @@ -1,9 +1,7 @@ 'use strict'; const reportEmailSender = require('./reportEmailSender'), - reportWebhookSender = require('./reportWebhookSender'), jobsManager = require('../../jobs/models/jobManager'), - statsFromatter = require('../../webhooks/models/statsFormatter'), aggregateReportGenerator = require('./aggregateReportGenerator'), logger = require('../../common/logger'), constants = require('../utils/constants'), @@ -30,7 +28,7 @@ module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { switch (stats.phase_status) { case constants.SUBSCRIBER_FAILED_STAGE: { logger.info(metadata, stats.error, 'handling error message'); - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FAILED, { report, stats }); + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_FAILED, { report, stats }); break; } case constants.SUBSCRIBER_STARTED_STAGE: { @@ -67,7 +65,7 @@ async function handleStart(report, job) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_STARTED_STAGE)) { return; } - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_STARTED, { report }); + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_STARTED, report); } async function handleFirstIntermediate(report, job) { @@ -76,7 +74,7 @@ async function handleFirstIntermediate(report, job) { } // WHAT DO WE DO WITH BATCHES let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_IN_PROGRESS, { report, aggregatedReport }); + webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_IN_PROGRESS, { report, aggregatedReport }); } async function handleDone(report, job, reportBenchmark) { @@ -84,44 +82,25 @@ async function handleDone(report, job, reportBenchmark) { return; } let emails = await getEmailTargets(job); - const { benchmarkThreshold, benchmarkWebhook } = await getBenchmarkConfig(); - - if (emails.length === 0 && benchmarkWebhook.length === 0) { - return; - } + const { benchmarkThreshold } = await getBenchmarkConfig(); let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - if (emails.length > 0) { - reportEmailSender.sendAggregateReport(aggregatedReport, job, emails, reportBenchmark); + if (emails && emails.length > 0) { + await reportEmailSender.sendAggregateReport(aggregatedReport, job, emails, reportBenchmark); } - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_FINISHED, { report, aggregatedReport, reportBenchmark }); - if (reportBenchmark.score && benchmarkThreshold) { const lastReports = await reportsManager.getReports(aggregatedReport.test_id); const lastScores = lastReports.slice(0, 3).filter(report => report.score).map(report => report.score.toFixed(1)); - let event = reportBenchmark.score < benchmarkThreshold ? WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED : WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED; - await webhooksManager.fireWebhookByEvent(job.id, event, { aggregatedReport, score: reportBenchmark.score, lastScores, icon: ':sad_1:' }); + const { event, icon } = reportBenchmark.score < benchmarkThreshold ? { event: WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, icon: ':sad_1:' } : { event: WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, icon: ':grin:' }; + await webhooksManager.fireWebhookByEvent(job, event, report, { aggregatedReport, score: reportBenchmark.score, lastScores, benchmarkThreshold }, { icon }); } + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_FINISHED, report, { aggregatedReport, score: reportBenchmark.score }, { icon: ':rocket:' }); } async function handleAbort(report, job) { - await webhooksManager.fireWebhookByEvent(job.id, WEBHOOK_EVENT_TYPE_ABORTED, { report }); -} - -async function getWebhookTargets(job) { - let targets = []; - let defaultWebhookUrl = await configHandler.getConfigValue(configConstants.DEFAULT_WEBHOOK_URL); - - if (defaultWebhookUrl) { - targets.push(defaultWebhookUrl); - } - - if (job.webhooks) { - targets = targets.concat(job.webhooks); - } - return targets; + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_ABORTED, { report }); } async function getBenchmarkConfig() { diff --git a/src/reports/models/reportWebhookSender.js b/src/reports/models/reportWebhookSender.js deleted file mode 100644 index 2c91281e2..000000000 --- a/src/reports/models/reportWebhookSender.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -let request = require('request-promise-native'); - -let logger = require('../../common/logger'); - -module.exports.send = async (webhooks, message, options = {}) => { - if (!webhooks) { - return; - } - let finallOptions = { - body: - { - 'text': message, - 'icon_emoji': options.icon || ':muscle:', - 'username': 'reporter' - }, - json: true - }; - let promises = []; - webhooks.forEach(webhookUrl => { - promises.push(request.post(Object.assign({ url: webhookUrl }, finallOptions))); - }); - - try { - await Promise.all(promises); - } catch (error) { - logger.error(error, 'Failed to send webhooks'); - } -}; diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 1563f2633..e7d4c62c2 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -41,6 +41,7 @@ async function getWebhook(webhookId) { return parseWebhook(webhook); } +// TEST THIS FUNCTION async function getAllGlobalWebhooks() { const webhooksModel = client.model('webhook'); const webhooks = await webhooksModel.findAll({ include: ['events'], where: { global: true } }); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 9ff5e0edd..bdc2b2f80 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -44,8 +44,9 @@ async function updateWebhook(webhookId, webhook) { return databaseConnector.updateWebhook(webhookId, webhook); }; +// TEST THIS FUNCTION async function getAllGlobalWebhooks() { - return databaseConnector.getAllWebhooks(); + return databaseConnector.getAllGlobalWebhooks(); } async function fireSingleWebhook(webhook, payload) { @@ -56,27 +57,34 @@ async function fireSingleWebhook(webhook, payload) { url: webhook.url, body: payload }); - logger.info(`Webhook fired successfully, url=${webhook.url}`); + logger.info(`Webhook fired successfully, url = ${webhook.url}`); } catch (requestError) { - logger.error(`Webhook failed, url=${webhook.url}`); + logger.error(`Webhook failed, url = ${webhook.url}`); throw requestError; } - return webhookResponse; } -async function fireWebhooks(webhooks, payload) { - return webhooks.map(webhook => fireSingleWebhook(webhook, webhooksFormatter[webhook.format_type](payload))); +//format, eventType, jobId, testId, report, additionalInfo = {}, options = {} +function fireWebhooks(webhooks, eventType, jobId, testId, report, additionalInfo, options) { + return webhooks.map(webhook => fireSingleWebhook(webhook, webhooksFormatter(webhook.format_type, eventType, jobId, testId, report, additionalInfo, options))); } -async function fireWebhookByEvent(jobId, eventType, payload) { - const job = await getWebhook(jobId); +// FAILED: report, stats +// STARTED: report +// IN_PROGRESS: report, aggregatedReport +// report, aggregatedReport, reportBenchmark +// BENCHMARK_FAILED/PASSED: report, aggregatedReport, score, lastScores, icon +// ABORTED: report +async function fireWebhookByEvent(job, eventType, report, additionalInfo = {}, options = {}) { + const jobWebhooks = await Promise.all(job.webhooks.map(webhookId => getWebhook(webhookId))); const globalWebhooks = await getAllGlobalWebhooks(); - const webhooks = [...job.webhooks, ...globalWebhooks]; - const webhooksWithEventType = webhooks.filter(webhook => webhook.events.include(eventType)); + const webhooks = [...jobWebhooks, ...globalWebhooks]; + const webhooksWithEventType = webhooks.filter(webhook => webhook.events.includes(eventType)); if (webhooksWithEventType.length === 0) { return; } - await Promise.allSettled(fireWebhooks(webhooksWithEventType, payload)); + const webhooksPromises = fireWebhooks(webhooksWithEventType, eventType, job.id, job.test_id, report, additionalInfo, options); + await Promise.allSettled(webhooksPromises); } module.exports = { @@ -86,4 +94,4 @@ module.exports = { deleteWebhook, updateWebhook, fireWebhookByEvent -}; \ No newline at end of file +}; diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index 237f6972d..ee90d41aa 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -1,3 +1,5 @@ +const cloneDeep = require('lodash/cloneDeep'); + const { EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK, @@ -19,6 +21,18 @@ function unknownWebhookEventTypeError(badWebhookEventTypeValue) { return new Error(`Unrecognized webhook event: ${badWebhookEventTypeValue}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`); } +function getThresholdSlackMessage(state, { testName, benchmarkThreshold, lastScores, aggregatedReport, score }) { + let resultText = 'above'; + let icon = ':rocket:'; + if (state === WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED) { + resultText = 'below'; + icon = ':sad_1:'; + } + return `${icon} *Test ${testName} got a score of ${score.toFixed(1)}` + + ` this is ${resultText} the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + + `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`;; +} + function slackWebhookFormat(message, options) { return { text: message, @@ -27,89 +41,92 @@ function slackWebhookFormat(message, options) { }; } -function json(event, testId, jobId, report, options) { +function json(event, testId, jobId, report, additionalInfo, options) { let payload = { test_id: testId, job_id: jobId, event: event, - additional_details: {} - }; - let additionalDetails = {}; - switch (event) { - case WEBHOOK_EVENT_TYPE_STARTED: { - additionalDetails = { - test_name: , - environment: , - duration: , - arrival_rate: , - parallelism: , - ramp_to: rampTo, - }; - break; - } - case WEBHOOK_EVENT_TYPE_FINISHED: { - additionalDetails = { - test_name: , - grafana_report: , - aggregated_report: { - ...aggregatedReport.aggregate - }, - report_benchmark: { - ...reportBenchmark - } - }; - break; - } - case WEBHOOK_EVENT_TYPE_FAILED: { - additionalDetails = { - environment: , - stats: { - ...stats.data - } - }; - break; - } - case WEBHOOK_EVENT_TYPE_ABORTED: { - additionalDetails = { - testName, - grafanaReport: - }; - break; - } - case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { - additionalDetails = { - aggregated_test_name: , - benchmark_threshold: , - score: , - last_three_scores: , - aggregated_report: - }; - break; - } - case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { - break; - } - case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { - additionalDetails = { + additional_details: { + ...cloneDeep({ report, ...additionalInfo }), - }; - break; } - case WEBHOOK_EVENT_TYPE_API_FAILURE: { - additionalDetails = { + }; + // let additionalDetails = {}; + // switch (event) { + // case WEBHOOK_EVENT_TYPE_STARTED: { + // additionalDetails = { + // test_name: , + // environment: , + // duration: , + // arrival_rate: , + // parallelism: , + // ramp_to: rampTo, + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_FINISHED: { + // additionalDetails = { + // test_name: , + // grafana_report: , + // aggregated_report: { + // ...aggregatedReport.aggregate + // }, + // report_benchmark: { + // ...reportBenchmark + // } + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_FAILED: { + // additionalDetails = { + // environment: , + // stats: { + // ...stats.data + // } + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_ABORTED: { + // additionalDetails = { + // testName, + // grafanaReport: + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { + // additionalDetails = { + // aggregated_test_name: , + // benchmark_threshold: , + // score: , + // last_three_scores: , + // aggregated_report: + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { + // break; + // } + // case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + // additionalDetails = { - }; - break; - } - default: { - throw unknownWebhookEventTypeError(); - } - } - payload.additional_details = additionalDetails; + // }; + // break; + // } + // case WEBHOOK_EVENT_TYPE_API_FAILURE: { + // additionalDetails = { + + // }; + // break; + // } + // default: { + // throw unknownWebhookEventTypeError(); + // } + // } + // payload.additional_details = additionalDetails; return payload; } -function slack(event, testId, jobId, report, options) { +function slack(event, testId, jobId, report, additionalInfo, options) { let message = null; const { environment, @@ -120,20 +137,16 @@ function slack(event, testId, jobId, report, options) { test_name: testName, grafana_report: grafanaReport } = report; - const { aggregatedReport, reportBenchmark } = options; + const { score, aggregatedReport, reportBenchmark, benchmarkThreshold, lastScores, stats } = additionalInfo; switch (event) { case WEBHOOK_EVENT_TYPE_STARTED: { - let rampToMessage = rampTo ? `, ramp to: ${rampTo} scenarios per second` : ''; + let rampToMessage = rampTo ? `ramp to: ${rampTo} scenarios per second` : ''; message = `🤓 *Test ${testName} with id: ${testId} has started*.\n - *test configuration:* environment: ${environment} duration: ${duration} seconds, arrival rate: ${arrivalRate} scenarios per second, number of runners: ${parallelism}${rampToMessage}`; + *test configuration:* environment: ${environment} duration: ${duration} seconds, arrival rate: ${arrivalRate} scenarios per second, number of runners: ${parallelism}, ${rampToMessage}`; break; } case WEBHOOK_EVENT_TYPE_FINISHED: { - message = `😎 *Test ${testName} with id: ${testId} is finished.*\n - ${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; - if (grafanaReport) { - message += `<${grafanaReport}|View final grafana dashboard report>`; - } + message = `😎 *Test ${testName} with id: ${testId} is finished.*\n ${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; break; } case WEBHOOK_EVENT_TYPE_FAILED: { @@ -145,43 +158,41 @@ function slack(event, testId, jobId, report, options) { } case WEBHOOK_EVENT_TYPE_ABORTED: { message = `😢 *Test ${testName} with id: ${testId} was aborted.*\n`; - if (grafanaReport) { - message += `<${grafanaReport}|View final grafana dashboard report>`; - } break; } - case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { - message = `:sad_1: *Test ${aggregatedtestName} got a score of ${score.toFixed(1)}` + - ` this is below the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + - `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; + case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: + case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + message = getThresholdSlackMessage(event, { testName, lastScores, benchmarkThreshold, score }); break; } case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { - break; - } - case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { + message = `:hammer_and_wrench: *Test ${testName} with id: ${testId} is in progress!*`; break; } case WEBHOOK_EVENT_TYPE_API_FAILURE: { + message = `::boom:: *Test ${testName} with id: ${testId} has encountered an API failure!*`; break; } default: { throw unknownWebhookEventTypeError(); } } - return slackWebhookFormat(message); + if (grafanaReport) { + message += `<${grafanaReport} | View final grafana dashboard report>`; + } + return slackWebhookFormat(message, options); } -module.exports = function(format, eventType, jobId, testId, report, options={}) { - switch(format) { +module.exports = function(format, eventType, jobId, testId, report, additionalInfo = {}, options = {}) { + switch (format) { case EVENT_FORMAT_TYPE_SLACK: { - return slack(eventType, testId, jobId, report, options); + return slack(eventType, testId, jobId, report, additionalInfo, options); } case EVENT_FORMAT_TYPE_JSON: { - return json(eventType, testId, jobId, report, options); + return json(eventType, testId, jobId, report, additionalInfo, options); } default: { - throw new Error(`Unrecognized webhook format: ${format}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`) + throw new Error(`Unrecognized webhook format: ${format}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`); } } -} \ No newline at end of file +}; diff --git a/tests/testExamples/Basic_test.json b/tests/testExamples/Basic_test.json index e57b432f8..6ab4f641d 100644 --- a/tests/testExamples/Basic_test.json +++ b/tests/testExamples/Basic_test.json @@ -32,4 +32,4 @@ }] }] } - } \ No newline at end of file +} diff --git a/tests/unit-tests/jobs/models/jobManager-test.js b/tests/unit-tests/jobs/models/jobManager-test.js index c59016ed3..73bd46a79 100644 --- a/tests/unit-tests/jobs/models/jobManager-test.js +++ b/tests/unit-tests/jobs/models/jobManager-test.js @@ -10,7 +10,6 @@ const should = require('should'), jobTemplate = require('../../../../src/jobs/models/kubernetes/jobTemplate'), config = require('../../../../src/common/consts').CONFIG; - let manager; const TEST_ID = '5a9eee73-cf56-47aa-ac77-fad59e961aaa'; @@ -109,6 +108,8 @@ const jobBodyWithCustomEnvVars = { max_virtual_users: 100 }; +// TODO: this tests suite runs over cassandra, cassandra should be dropped + describe('Manager tests', function () { let sandbox; let cassandraInsertStub; @@ -160,7 +161,7 @@ describe('Manager tests', function () { getConfigValueStub.withArgs(config.JOB_PLATFORM).returns('KUBERNETES'); }); - beforeEach(async () => { + beforeEach(async () => { await manager.init(); sandbox.resetHistory(); }); diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index a70371998..c737f6c08 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -2,6 +2,8 @@ let sinon = require('sinon'); let rewire = require('rewire'); let should = require('should'); +const cloneDeep = require('lodash/cloneDeep'); + let databaseConfig = require('../../../../src/config/databaseConfig'); let sequelizeConnector = rewire('../../../../src/jobs/models/database/sequelize/sequelizeConnector'); @@ -18,6 +20,8 @@ describe('Sequelize client tests', function () { let sequelizeUpdateStub; let sequelizeGetStub; let sequelizeDestroyStub; + let sequelizeTransactionStub; + const transaction = {}; before(() => { sandbox = sinon.sandbox.create(); @@ -39,7 +43,7 @@ describe('Sequelize client tests', function () { sequelizeCloseStub = sandbox.stub(); sequelizeStub = sandbox.stub(); sequelizeCloseStub = sandbox.stub(); - sequelizeStub = sandbox.stub(); + sequelizeTransactionStub = sandbox.stub(); sequelizeDefineStub.returns({ hasMany: () => { @@ -56,14 +60,16 @@ describe('Sequelize client tests', function () { create: sequelizeCreateStub, update: sequelizeUpdateStub, findAll: sequelizeGetStub, - destroy: sequelizeDestroyStub + destroy: sequelizeDestroyStub, + findByPk: sequelizeGetStub }); sequelizeStub.returns({ authenticate: sequelizeAuthenticateStub, model: sequelizeModelStub, define: sequelizeDefineStub, - close: sequelizeCloseStub + close: sequelizeCloseStub, + transaction: sequelizeTransactionStub }); sequelizeStub.DataTypes = {}; sequelizeConnector.__set__('Sequelize', sequelizeStub); @@ -73,6 +79,7 @@ describe('Sequelize client tests', function () { }); afterEach(() => { sandbox.resetHistory(); + sandbox.resetBehavior(); }); after(() => { @@ -93,7 +100,7 @@ describe('Sequelize client tests', function () { let id = uuid.v4(); let testId = uuid.v4(); - await sequelizeConnector.insertJob(id, { + const job = { test_id: testId, arrival_rate: 1, duration: 1, @@ -101,44 +108,45 @@ describe('Sequelize client tests', function () { emails: ['hello@zooz.com', 'hello@payu.com'], environment: 'test', ramp_to: '1', - webhooks: ['http://zooz.com', 'http://payu.com'], + webhooks: ['UUIDSTUB'], parallelism: 4, max_virtual_users: 100, notes: 'some nice notes', proxy_url: 'http://proxy.com', debug: '*', enabled: true - }); + }; + const createdJob = { + dataValues: { + ...job, + id, + webhooks: ['UUIDSTUB'], + emails: [{ + 'id': 'UUIDSTUB', + 'address': 'hello@zooz.com' + }, { + 'id': 'UUIDSTUB', + 'address': 'hello@payu.com' + }] + }, + setWebhooks: sandbox.stub() + }; - should(sequelizeCreateStub.args[0][0]).eql({ - 'id': id, - 'test_id': testId, - 'arrival_rate': 1, - 'cron_expression': '* * * *', - 'duration': 1, - 'environment': 'test', - 'ramp_to': '1', - 'parallelism': 4, - 'notes': 'some nice notes', - 'max_virtual_users': 100, - 'proxy_url': 'http://proxy.com', - 'debug': '*', - 'enabled': true, - 'webhooks': [{ - 'id': 'UUIDSTUB', - 'url': 'http://zooz.com' - }, { - 'id': 'UUIDSTUB', - 'url': 'http://payu.com' - }], - 'emails': [{ - 'id': 'UUIDSTUB', - 'address': 'hello@zooz.com' - }, { - 'id': 'UUIDSTUB', - 'address': 'hello@payu.com' - }] - }); + createdJob.setWebhooks.resolves(); + sequelizeCreateStub.resolves(createdJob); + sequelizeTransactionStub.resolves(); + + await sequelizeConnector.insertJob(id, job); + await sequelizeTransactionStub.yields(transaction); + + const jobParams = cloneDeep(job); + jobParams.emails = createdJob.dataValues.emails; + jobParams.id = createdJob.dataValues.id; + delete jobParams.webhooks; + + should(sequelizeCreateStub.args[0][0]).deepEqual(jobParams); + should(createdJob.setWebhooks.calledOnce).eql(true); + should(createdJob.setWebhooks.args[0][0]).deepEqual(job.webhooks); }); it('should succeed insert without webhooks and emails', async () => { @@ -147,41 +155,47 @@ describe('Sequelize client tests', function () { let id = uuid.v4(); let testId = uuid.v4(); - await sequelizeConnector.insertJob(id, { + const job = { test_id: testId, arrival_rate: 1, duration: 1, cron_expression: '* * * *', environment: 'test', ramp_to: '1', + enabled: true, parallelism: 4, max_virtual_users: 100, notes: 'some notes', proxy_url: 'http://proxy.com', debug: '*' - }); + }; - should(sequelizeCreateStub.args[0][0]).eql({ - 'id': id, - 'test_id': testId, - 'arrival_rate': 1, - 'cron_expression': '* * * *', - 'duration': 1, - 'environment': 'test', - 'ramp_to': '1', - 'webhooks': undefined, - 'emails': undefined, - 'notes': 'some notes', - 'parallelism': 4, - 'max_virtual_users': 100, - 'proxy_url': 'http://proxy.com', - 'debug': '*', - 'enabled': undefined - }); + const createdJob = { + dataValues: { + ...job, + id + }, + setWebhooks: sandbox.stub() + }; + + sequelizeCreateStub.resolves(createdJob); + createdJob.setWebhooks.resolves(); + sequelizeTransactionStub.resolves(); + + await sequelizeConnector.insertJob(id, job); + await sequelizeTransactionStub.yield(transaction); + + const jobParams = cloneDeep(job); + jobParams.emails = createdJob.dataValues.emails; + jobParams.id = createdJob.dataValues.id; + + should(sequelizeCreateStub.args[0][0]).eql(jobParams); + should(createdJob.setWebhooks.calledOnce).eql(true); + should(createdJob.setWebhooks.args[0][0]).deepEqual([]); }); it('should log error for failing inserting new test', async () => { - sequelizeCreateStub.rejects(new Error('Sequelize Error')); + sequelizeTransactionStub.rejects(new Error('Sequelize Error')); await sequelizeConnector.init(sequelizeStub()); @@ -207,57 +221,76 @@ describe('Sequelize client tests', function () { let sequelizeResponse = [{ dataValues: { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'environment': 'test', - 'cron_expression': null, - 'arrival_rate': 100, - 'duration': 1700, - 'ramp_to': null, - 'webhooks': [{ - dataValues: { - 'id': '8138e406-0d5f-4caf-a143-a758b9545b75', - 'url': 'https://hooks.slack.com/services/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', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' - } - }, { - dataValues: { - 'id': 'e38b985f-efec-4315-93bf-6f04eb2b7438', - 'url': 'http://www.one.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + environment: 'test', + cron_expression: null, + arrival_rate: 100, + duration: 1700, + ramp_to: null, + webhooks: [ + { + dataValues: { + id: '8138e406-0d5f-4caf-a143-a758b9545b75', + name: 'avi3', + url: 'https://1f19d804b781f93bfe92e6dac1b96572.m.pipedream.net', + global: false, + format_type: 'json', + created_at: '2020-08-10T20:37:49.313Z', + updated_at: '2020-08-10T20:37:49.313Z', + webhook_job_mapping: { + created_at: '2020-08-11T16:12:35.634Z', + updated_at: '2020-08-11T16:12:35.634Z', + webhook_id: '04e844d6-c0c4-46e9-a752-ba66035e047c', + job_id: 'c941ea0f-7b3d-4b05-a01a-7961c7735e04' + } + } + }, + { + dataValues: { + id: 'e38b985f-efec-4315-93bf-6f04eb2b7438', + name: 'avi', + url: 'https://1f19d804b781f93bfe92e6dac1b96572.m.pipedream.net', + global: false, + format_type: 'json', + created_at: '2020-08-10T20:10:26.573Z', + updated_at: '2020-08-10T20:10:26.573Z', + webhook_job_mapping: { + created_at: '2020-08-11T16:12:35.634Z', + updated_at: '2020-08-11T16:12:35.634Z', + webhook_id: '5a862394-6e13-45e0-a478-7cd069a2d438', + job_id: 'c941ea0f-7b3d-4b05-a01a-7961c7735e04' + } + } } - }], - 'emails': [{ + ], + emails: [{ dataValues: { - 'id': '8bd6a285-9d9f-4e07-a8e3-387f5936c347', - 'address': 'eli.nudler@zooz.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: '8bd6a285-9d9f-4e07-a8e3-387f5936c347', + address: 'eli.nudler@zooz.com', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }, { dataValues: { - 'id': 'edc77399-ea72-4b0a-97da-c6169e59bb52', - 'address': 'xyz@om.ds', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'edc77399-ea72-4b0a-97da-c6169e59bb52', + address: 'xyz@om.ds', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }] } }, { dataValues: { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59d', - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', - 'environment': 'test', - 'cron_expression': null, - 'arrival_rate': 200, - 'duration': 2000, - 'ramp_to': null + id: 'd6b0f076-2efb-48e1-82d2-82250818f59d', + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', + environment: 'test', + cron_expression: null, + arrival_rate: 200, + duration: 2000, + ramp_to: null } }]; @@ -266,32 +299,32 @@ describe('Sequelize client tests', function () { should(jobs.length).eql(2); should(jobs[0]).eql({ - 'arrival_rate': 100, - 'cron_expression': null, - 'duration': 1700, - 'emails': [ + arrival_rate: 100, + cron_expression: null, + duration: 1700, + emails: [ 'eli.nudler@zooz.com', 'xyz@om.ds' ], - 'environment': 'test', - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'ramp_to': null, - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'webhooks': [ - 'https://hooks.slack.com/services/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', - 'http://www.one.com' + environment: 'test', + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + ramp_to: null, + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + webhooks: [ + '8138e406-0d5f-4caf-a143-a758b9545b75', + 'e38b985f-efec-4315-93bf-6f04eb2b7438' ] }); should(jobs[1]).eql({ - 'arrival_rate': 200, - 'cron_expression': null, - 'duration': 2000, - 'environment': 'test', - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59d', - 'ramp_to': null, - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', - 'webhooks': undefined, - 'emails': undefined + arrival_rate: 200, + cron_expression: null, + duration: 2000, + environment: 'test', + id: 'd6b0f076-2efb-48e1-82d2-82250818f59d', + ramp_to: null, + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a7', + webhooks: undefined, + emails: undefined }); }); @@ -326,45 +359,45 @@ describe('Sequelize client tests', function () { let sequelizeResponse = [{ dataValues: { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'environment': 'test', - 'cron_expression': null, - 'arrival_rate': 100, - 'duration': 1700, - 'ramp_to': null, - 'webhooks': [{ + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + environment: 'test', + cron_expression: null, + arrival_rate: 100, + duration: 1700, + ramp_to: null, + webhooks: [{ dataValues: { - 'id': '8138e406-0d5f-4caf-a143-a758b9545b75', - 'url': 'https://hooks.slack.com/services/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', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: '8138e406-0d5f-4caf-a143-a758b9545b75', + url: 'https://hooks.slack.com/services/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', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }, { dataValues: { - 'id': 'e38b985f-efec-4315-93bf-6f04eb2b7438', - 'url': 'http://www.one.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'e38b985f-efec-4315-93bf-6f04eb2b7438', + url: 'http://www.one.com', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }], - 'emails': [{ + emails: [{ dataValues: { - 'id': '8bd6a285-9d9f-4e07-a8e3-387f5936c347', - 'address': 'eli.nudler@zooz.com', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: '8bd6a285-9d9f-4e07-a8e3-387f5936c347', + address: 'eli.nudler@zooz.com', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }, { dataValues: { - 'id': 'edc77399-ea72-4b0a-97da-c6169e59bb52', - 'address': 'xyz@om.ds', - 'created_at': '2019-01-20T12:56:31.000Z', - 'updated_at': '2019-01-20T12:56:31.000Z', - 'job_id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + id: 'edc77399-ea72-4b0a-97da-c6169e59bb52', + address: 'xyz@om.ds', + created_at: '2019-01-20T12:56:31.000Z', + updated_at: '2019-01-20T12:56:31.000Z', + job_id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }] } @@ -374,36 +407,36 @@ describe('Sequelize client tests', function () { let jobs = await sequelizeConnector.getJob('d6b0f076-2efb-48e1-82d2-82250818f59c'); should(jobs).eql([{ - 'arrival_rate': 100, - 'cron_expression': null, - 'duration': 1700, - 'emails': [ + arrival_rate: 100, + cron_expression: null, + duration: 1700, + emails: [ 'eli.nudler@zooz.com', 'xyz@om.ds' ], - 'environment': 'test', - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c', - 'ramp_to': null, - 'test_id': 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', - 'webhooks': [ - 'https://hooks.slack.com/services/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', - 'http://www.one.com' + environment: 'test', + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c', + ramp_to: null, + test_id: 'e2340c63-7828-4b69-b79d-6cbea8fec7a6', + webhooks: [ + '8138e406-0d5f-4caf-a143-a758b9545b75', + 'e38b985f-efec-4315-93bf-6f04eb2b7438' ] }]); should(sequelizeGetStub.args[0][0]).eql({ - 'attributes': { - 'exclude': [ + attributes: { + exclude: [ 'updated_at', 'created_at' ] }, - 'include': [ + include: [ {}, 'webhooks' ], - 'where': { - 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' + where: { + id: 'd6b0f076-2efb-48e1-82d2-82250818f59c' } }); }); @@ -464,44 +497,45 @@ describe('Sequelize client tests', function () { let id = uuid.v4(); let testId = uuid.v4(); + const webhookId = uuid.v4(); - await sequelizeConnector.updateJob(id, { - test_id: testId, - arrival_rate: 1, - duration: 1, - cron_expression: '* * * *', - environment: 'test', - ramp_to: '1', - max_virtual_users: 500, - parallelism: 3, - proxy_url: 'http://proxy.com', - debug: '*', - enabled: false - }); - - should(sequelizeUpdateStub.args[0][0]).eql({ - 'test_id': testId, - 'arrival_rate': 1, - 'cron_expression': '* * * *', - 'duration': 1, - 'environment': 'test', - 'ramp_to': '1', - 'max_virtual_users': 500, - 'parallelism': 3, - 'proxy_url': 'http://proxy.com', - 'debug': '*', - 'enabled': false - }); - - should(sequelizeUpdateStub.args[0][1]).eql({ - 'where': { - 'id': id - } - }); + const sequelizeJobResponse = { + dataValues: { + test_id: testId, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * *', + environment: 'test', + ramp_to: '1', + max_virtual_users: 500, + parallelism: 3, + proxy_url: 'http://proxy.com', + debug: '*', + enabled: false + }, + setWebhooks: sandbox.stub() + }; + const updatedJobInfo = { + ...sequelizeJobResponse.dataValues, + cron_expression: '5 4 * *', + proxy_url: 'http://predator.dev', + webhooks: [ webhookId ] + }; + sequelizeGetStub.resolves(sequelizeJobResponse); + sequelizeTransactionStub.resolves(); + sequelizeJobResponse.setWebhooks.resolves(); + + await sequelizeConnector.updateJob(id, updatedJobInfo); + await sequelizeTransactionStub.yield(transaction); + + const { webhooks, ...updatedJobInfoWithoutWebhooks } = updatedJobInfo; + + should(sequelizeUpdateStub.args[0][0]).eql(updatedJobInfoWithoutWebhooks); + should(sequelizeUpdateStub.args[0][1]).eql({ where: { id }, transaction }); }); it('should log error for failing updating test', async () => { - sequelizeUpdateStub.rejects(new Error('Sequelize Error')); + sequelizeTransactionStub.rejects(new Error('Sequelize Error')); await sequelizeConnector.init(sequelizeStub()); @@ -520,4 +554,4 @@ describe('Sequelize client tests', function () { } }); }); -}); \ No newline at end of file +}); From 847f06465eb8232c3db704b77b93f54bb4255eee Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 24 May 2020 10:34:21 +0300 Subject: [PATCH 22/38] docs(webhooks): create webhooks API spec (#308) * docs(webhooks): create webhooks API spec --- docs/openapi3.yaml | 171 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 1d912536b..6fbec0258 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -43,6 +43,9 @@ tags: - name: Benchmarks description: | By creating a benchmark for a specific test, each subsequent test run for that test will be given a score from 0-100 summarizing the test run in one simple to analyze numerical value. + - name: Webhooks + description: | + This resource allows you to configure webhooks. x-tagGroups: - name: Reference tags: @@ -54,6 +57,7 @@ x-tagGroups: - Configuration - Files - Benchmarks + - Webhooks paths: #DSL Definitions @@ -1382,6 +1386,124 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + # Webhooks + /v1/webhooks: + get: + operationId: retrieve-webhooks + tags: + - Webhooks + summary: Retrieve webhooks + description: Retrieve all webhooks. + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + post: + operationId: create-a-webhook + tags: + - Webhooks + summary: Create a Webhook + description: Create a new Webhook. + responses: + '201': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + description: The webhook to add + required: true + /v1/webhooks/{webhook_id}: + get: + operationId: retrieve-a-webhook + tags: + - Webhooks + summary: Retrieve a webhook by id + description: Retrieve a webhook by id. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + put: + operationId: update-a-webhook + tags: + - Webhooks + summary: Update a webhook + description: Update a webhook. + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' # Files /v1/files: @@ -1944,7 +2066,8 @@ components: The event body will include detailed information about the test, such as the number of scenarios that were executed and the number of requests that were invoked. items: type: string - description: The url of where to send the webhook with the report information + format: uuid + description: The id of the webhook arrival_rate: type: number minimum: 1 @@ -2381,3 +2504,49 @@ components: filename: type: string description: the name of the file + type: number + description: benchmark percentage weight + webhook: + type: object + required: + - name + - webhook_url + - events + - format_type + properties: + id: + description: Unique webhook identifier + type: string + format: uuid + readOnly: true + name: + type: string + description: Webhook name + webhook_url: + type: string + description: Webhook url to post events + events: + description: list of events which will trigger the webhook + type: array + items: + $ref: '#/components/schemas/webhooks_types' + format_type: + $ref: '#/components/schemas/webhook_format_types' + global: + type: boolean + description: indicates whether the webhook should be applied globally(over all jobs) + webhooks_types: + type: string + enum: + - started + - api_failure + - aborted + - failed + - finished + - benchmark_passed + - benchmark_failed + webhook_format_types: + type: string + enum: + - slack + - json From 47c3df7835c85ad2c04737d90aadc152567363be Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 15 Jun 2020 10:23:30 +0300 Subject: [PATCH 23/38] implement GET /webhooks (#318) --- .eslintrc.json | 4 +- docs/openapi3.yaml | 9 +- package-lock.json | 2 +- src/app.js | 2 + src/common/consts.js | 32 +++++++ .../migrations/04_webhooks.js | 62 ++++++++++++ src/database/sequlize-handler/sequlize.js | 3 + .../database/sequelize/sequelizeConnector.js | 29 +++--- .../controllers/webhooksController.js | 12 +++ .../database/cassandra/cassandraConnector.js | 16 ++++ .../models/database/databaseConnector.js | 21 ++++ .../database/sequelize/sequelizeConnector.js | 69 ++++++++++++++ src/webhooks/models/webhookManager.js | 8 ++ src/webhooks/routes/webhooksRouter.js | 11 +++ .../jobs/sequelize/sequelizeConnector-test.js | 2 +- .../sequelize/sequelizeConnector-test.js | 95 +++++++++++++++++++ 16 files changed, 354 insertions(+), 23 deletions(-) create mode 100644 src/database/sequlize-handler/migrations/04_webhooks.js create mode 100644 src/webhooks/controllers/webhooksController.js create mode 100644 src/webhooks/models/database/cassandra/cassandraConnector.js create mode 100644 src/webhooks/models/database/databaseConnector.js create mode 100644 src/webhooks/models/database/sequelize/sequelizeConnector.js create mode 100644 src/webhooks/models/webhookManager.js create mode 100644 src/webhooks/routes/webhooksRouter.js create mode 100644 tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js diff --git a/.eslintrc.json b/.eslintrc.json index 3d06b8857..6f95b6671 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,8 @@ "plugins": [], "env": { "node": true, - "mocha": true + "mocha": true, + "es6": true }, "parserOptions": { "ecmaVersion": 8, @@ -20,4 +21,3 @@ "camelcase": ["warn"] } } - \ No newline at end of file diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 6fbec0258..f71549fe7 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -2062,8 +2062,9 @@ components: webhooks: type: array description: | - A URL to which an event will be sent when the test execution is completed. - The event body will include detailed information about the test, such as the number of scenarios that were executed and the number of requests that were invoked. + An array of webhooks ids, events will be fired to the coresponding webhooks according to their events configuration. + The event body will include detailed information about the test, such as the number of scenarios that were executed + and the number of requests that were invoked. items: type: string format: uuid @@ -2510,7 +2511,7 @@ components: type: object required: - name - - webhook_url + - url - events - format_type properties: @@ -2522,7 +2523,7 @@ components: name: type: string description: Webhook name - webhook_url: + url: type: string description: Webhook url to post events events: diff --git a/package-lock.json b/package-lock.json index 86b832804..4d72c0ab0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11067,7 +11067,7 @@ }, "streamsearch": { "version": "0.1.2", - "resolved": "http://npm.zooz.co:8083/streamsearch/-/streamsearch-0.1.2.tgz", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, "string-width": { diff --git a/src/app.js b/src/app.js index 1253e1e95..4414bfacc 100644 --- a/src/app.js +++ b/src/app.js @@ -10,6 +10,7 @@ let dslRouter = require('./tests/routes/dslRoute.js'); let testsRouter = require('./tests/routes/testsRoute.js'); let processorsRouter = require('./processors/routes/processorsRoute.js'); let filesRouter = require('./files/routes/filesRoute.js'); +let webhooksRouter = require('./webhooks/routes/webhooksRouter'); let swaggerValidator = require('express-ajv-swagger-validation'); let audit = require('express-requests-logger'); @@ -66,6 +67,7 @@ module.exports = async () => { app.use('/v1/tests', testsRouter); app.use('/v1/processors', processorsRouter); app.use('/v1/files', filesRouter); + app.use('/v1/webhooks', webhooksRouter); app.use('/', function (req, res, next) { res.redirect('/ui'); diff --git a/src/common/consts.js b/src/common/consts.js index 8db47fce3..ebf0b2de2 100644 --- a/src/common/consts.js +++ b/src/common/consts.js @@ -1,7 +1,39 @@ +const EVENT_FORMAT_TYPE_SLACK = 'slack'; +const EVENT_FORMAT_TYPE_JSON = 'json'; +const WEBHOOK_EVENT_TYPE_STARTED = 'started'; +const WEBHOOK_EVENT_TYPE_FINISHED = 'finished'; +const WEBHOOK_EVENT_TYPE_API_FAILURE = 'api_failure'; +const WEBHOOK_EVENT_TYPE_ABORTED = 'aborted'; +const WEBHOOK_EVENT_TYPE_FAILED = 'failed'; +const WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED = 'benchmark_passed'; +const WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED = 'benchmark_failed'; + module.exports = { TEST_TYPE_BASIC: 'basic', TEST_TYPE_DSL: 'dsl', PROCESSOR_FUNCTIONS_KEYS: ['beforeScenario', 'afterScenario', 'beforeRequest', 'afterResponse'], + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + EVENT_FORMAT_TYPE_SLACK, + EVENT_FORMAT_TYPE_JSON, + EVENT_FORMAT_TYPES: [ + EVENT_FORMAT_TYPE_SLACK, + EVENT_FORMAT_TYPE_JSON + ], + WEBHOOK_EVENT_TYPES: [ + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED + ], ERROR_MESSAGES: { NOT_FOUND: 'Not found', DSL_DEF_ALREADY_EXIST: 'Definition already exists', diff --git a/src/database/sequlize-handler/migrations/04_webhooks.js b/src/database/sequlize-handler/migrations/04_webhooks.js new file mode 100644 index 000000000..88a3a4a8a --- /dev/null +++ b/src/database/sequlize-handler/migrations/04_webhooks.js @@ -0,0 +1,62 @@ +const Sequelize = require('sequelize'); +const uuid = require('uuid'); +const { WEBHOOK_EVENT_TYPES } = require('../../../common/consts'); + +const tableName = 'webhooks'; +const webhookEventsTableName = 'webhook_events'; +const webhookEventMappingTableName = 'webhook_events'; +const webhookJobsMappingTableName = 'webhook_job_mapping'; +const columns = [ + { + name: 'name', + dt: Sequelize.DataTypes.TEXT('medium') + }, + { + name: 'global', + dt: Sequelize.DataTypes.BOOLEAN + } +]; + +async function takeActionOnColumn(describedTable, newColumnName, existAsyncAction, notExistAsyncAction) { + if (describedTable[newColumnName]) { + return existAsyncAction(); + } + return notExistAsyncAction(); +} + +function createEnumRow(name) { + return { + name, + id: uuid(), + created_at: new Date(), + updated_at: new Date() + }; +} + +module.exports.up = async (query, DataTypes) => { + let describedWebhooks = await query.describeTable(tableName); + const webhooksEventTypes = WEBHOOK_EVENT_TYPES.map(createEnumRow); + const promises = [ + ...columns.map(({ name, dt }) => + takeActionOnColumn( + describedWebhooks, + name, + () => null, + () => query.addColumn(tableName, name, dt) + ) + ) + ]; + await Promise.all(promises); + await query.bulkUpdate(tableName, { name: 'Webhook', global: false }); + await query.bulkInsert(webhookEventsTableName, webhooksEventTypes); +}; + +module.exports.down = async (query, DataTypes) => { + const promises = [ + ...columns.map(({ name }) => query.removeColumn(tableName, name)), + query.dropTable(webhookEventMappingTableName), + query.dropTable(webhookEventsTableName), + query.dropTable(webhookJobsMappingTableName) + ]; + await Promise.all(promises); +}; diff --git a/src/database/sequlize-handler/sequlize.js b/src/database/sequlize-handler/sequlize.js index 1af946d5c..ca05fa48d 100644 --- a/src/database/sequlize-handler/sequlize.js +++ b/src/database/sequlize-handler/sequlize.js @@ -10,11 +10,13 @@ const processorsSequlizeConnector = require('../../processors/models/database/se const fileSequlizeConnector = require('../../files/models/database/sequelize/sequelizeConnector'); const logger = require('../../../src/common/logger'); const databaseConfig = require('../../config/databaseConfig'); +const webhooksSequlizeConnector = require('../../webhooks/models/database/sequelize/sequelizeConnector'); const Sequelize = require('sequelize'); let sequlizeClient; module.exports.init = async () => { sequlizeClient = await createClient(); + await webhooksSequlizeConnector.init(sequlizeClient); await schedulerSequlizeConnector.init(sequlizeClient); await reportsSequlizeConnector.init(sequlizeClient); await testsSequlizeConnector.init(sequlizeClient); @@ -22,6 +24,7 @@ module.exports.init = async () => { await processorsSequlizeConnector.init(sequlizeClient); await fileSequlizeConnector.init(sequlizeClient); await runSequlizeMigrations(); + await sequlizeClient.sync(); }; module.exports.ping = async () => { diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index 6f4cab06f..c56c18c55 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -34,7 +34,7 @@ async function insertJob(jobId, jobInfo) { proxy_url: jobInfo.proxy_url, enabled: jobInfo.enabled, debug: jobInfo.debug, - webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { + webhooks: jobInfo.webhooks ? jobInfo.webhooks.map(webhookUrl => { // still missing data attributes(name, global, format_type) return { id: uuid(), url: webhookUrl }; }) : undefined, emails: jobInfo.emails ? jobInfo.emails.map(emailAddress => { @@ -57,7 +57,7 @@ async function getJobsAndParse(jobId) { let options = { attributes: { exclude: ['updated_at', 'created_at'] }, - include: [job.webhook, job.email] + include: [job.email, 'webhooks'] }; if (jobId) { @@ -119,16 +119,6 @@ async function deleteJob(jobId) { } async function initSchemas() { - const webhook = client.define('webhook', { - id: { - type: Sequelize.DataTypes.UUID, - primaryKey: true - }, - url: { - type: Sequelize.DataTypes.STRING - } - }); - const email = client.define('email', { id: { type: Sequelize.DataTypes.UUID, @@ -181,10 +171,19 @@ async function initSchemas() { type: Sequelize.DataTypes.BOOLEAN } }); - - job.webhook = job.hasMany(webhook); job.email = job.hasMany(email); await job.sync(); - await webhook.sync(); await email.sync(); + + const webhooks = client.model('webhook'); + webhooks.belongsToMany(job, { + through: 'webhook_job_mapping', + as: 'jobs', + foreignKey: 'webhook_id' + }); + job.belongsToMany(webhooks, { + through: 'webhook_job_mapping', + as: 'webhooks', + foreignKey: 'job_id' + }); } \ No newline at end of file diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js new file mode 100644 index 000000000..aa068ad11 --- /dev/null +++ b/src/webhooks/controllers/webhooksController.js @@ -0,0 +1,12 @@ +'use strict'; +let webhookManager = require('../models/webhookManager'); + +module.exports.getAllWebhooks = async function (req, res, next) { + let webhooks; + try { + webhooks = await webhookManager.getAllWebhooks(); + return res.status(200).json(webhooks); + } catch (err) { + return next(err); + } +}; diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js new file mode 100644 index 000000000..b22b54435 --- /dev/null +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -0,0 +1,16 @@ +let logger = require('../../../../common/logger'); + +module.exports = { + init, + getAllWebhooks +}; + +async function init() { + const errorMessage = 'Webhooks feature is not implemented over Cassandra'; + logger.fatal(errorMessage); + throw new Error(errorMessage); +} + +async function getAllWebhooks() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js new file mode 100644 index 000000000..de1c28e3f --- /dev/null +++ b/src/webhooks/models/database/databaseConnector.js @@ -0,0 +1,21 @@ +let databaseConfig = require('../../../config/databaseConfig'); +let cassandraConnector = require('./cassandra/cassandraConnector'); +let sequelizeConnector = require('./sequelize/sequelizeConnector'); +let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +module.exports = { + init, + getAllWebhooks, + closeConnection +}; + +async function init() { + return databaseConnector.init(); +} + +function closeConnection() { + return databaseConnector.closeConnection(); +} + +async function getAllWebhooks(from, limit, exclude) { + return databaseConnector.getAllWebhooks(from, limit, exclude); +} \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js new file mode 100644 index 000000000..0045016b4 --- /dev/null +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -0,0 +1,69 @@ +'use strict'; + +const Sequelize = require('sequelize'); +let client; + +module.exports = { + init, + getAllWebhooks +}; + +async function init(sequelizeClient) { + client = sequelizeClient; + await initSchemas(); +} + +async function getAllWebhooks() { + const webhooksModel = client.model('webhook'); + return webhooksModel.findAll({ include: ['events'] }); +} + +async function initSchemas() { + const webhooksSchema = client.define('webhook', { + id: { + type: Sequelize.DataTypes.UUID, + primaryKey: true + }, + name: { + type: Sequelize.DataTypes.TEXT('medium') + }, + url: { + type: Sequelize.DataTypes.STRING + }, + global: { + type: Sequelize.DataTypes.BOOLEAN + }, + format_type: { + type: Sequelize.DataTypes.STRING + }, + created_at: { + type: Sequelize.DataTypes.DATE + }, + updated_at: { + type: Sequelize.DataTypes.DATE + } + }); + const webhooksEvents = client.define('webhook_event', { + id: { + type: Sequelize.DataTypes.UUID, + primaryKey: true + }, + name: { + type: Sequelize.DataTypes.TEXT('medium') + } + }); + + webhooksSchema.belongsToMany(webhooksEvents, { + through: 'webhook_event_mapping', + as: 'events', + foreignKey: 'webhook_id' + }); + webhooksEvents.belongsToMany(webhooksSchema, { + through: 'webhook_event_mapping', + as: 'webhooks', + foreignKey: 'webhook_event_id' + }); + + await webhooksSchema.sync(); + await webhooksEvents.sync(); +} diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js new file mode 100644 index 000000000..0fbe56dd9 --- /dev/null +++ b/src/webhooks/models/webhookManager.js @@ -0,0 +1,8 @@ +'use strict'; + +const databaseConnector = require('./database/databaseConnector'); + +module.exports.getAllWebhooks = async function () { + let getAllWebhooks = await databaseConnector.getAllWebhooks(); + return getAllWebhooks; +}; diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js new file mode 100644 index 000000000..9a5a24ec4 --- /dev/null +++ b/src/webhooks/routes/webhooksRouter.js @@ -0,0 +1,11 @@ +'use strict'; + +let swaggerValidator = require('express-ajv-swagger-validation'); +let express = require('express'); +let router = express.Router(); + +let webhooksController = require('../controllers/webhooksController'); + +router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); + +module.exports = router; diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index e96f8d394..16b5b1c0e 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -80,7 +80,7 @@ describe('Sequelize client tests', function () { describe('Init tests', () => { it('it should initialize sequelize with mysql client successfully', async () => { await sequelizeConnector.init(sequelizeStub()); - should(sequelizeDefineStub.calledThrice).eql(true); + should(sequelizeDefineStub.calledTwice).eql(true); }); }); diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js new file mode 100644 index 000000000..f0adad6a9 --- /dev/null +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -0,0 +1,95 @@ +'use strict'; +const sinon = require('sinon'), + { expect } = require('chai'), + databaseConfig = require('../../../../src/config/databaseConfig'), + sequelizeConnector = require('../../../../src/webhooks/models/database/sequelize/sequelizeConnector'); + +describe('Sequelize client tests', function () { + const webhookRaw = { + id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', + name: 'my special webhook', + url: 'http://callback.com', + global: false, + format_type: 'json', + created_at: '2020-06-13T13:13:16.763Z', + updated_at: '2020-06-13T13:13:16.763Z', + events: [] + }; + + let sandbox, + sequelizeModelStub, + sequelizeDeleteStub, + sequelizeDefineStub, + sequelizeGeValueStub, + sequelizeGetStub, + sequelizeCreateStub, + sequelizeUpdateStub, + sequelizeBelongsToMany; + + before(async () => { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(async () => { + databaseConfig.type = 'SQLITE'; + databaseConfig.name = 'predator'; + databaseConfig.username = 'username'; + databaseConfig.password = 'password'; + + sequelizeModelStub = sandbox.stub(); + sequelizeDefineStub = sandbox.stub(); + sequelizeGetStub = sandbox.stub(); + sequelizeDeleteStub = sandbox.stub(); + sequelizeGeValueStub = sandbox.stub(); + sequelizeCreateStub = sandbox.stub(); + sequelizeUpdateStub = sandbox.stub(); + sequelizeBelongsToMany = sandbox.stub(); + + sequelizeDefineStub.returns({ + hasMany: () => { + }, + sync: () => { + }, + belongsToMany: () => { + + } + }); + + sequelizeModelStub.returns({ + key: {}, + value: {}, + findAll: sequelizeGetStub, + findOne: sequelizeGeValueStub, + destroy: sequelizeDeleteStub, + create: sequelizeCreateStub + }); + + await sequelizeConnector.init({ + model: sequelizeModelStub, + define: sequelizeDefineStub + }); + }); + + afterEach(() => { + sandbox.resetHistory(); + }); + + after(() => { + sandbox.restore(); + }); + + describe('getAllWebhooks', function() { + describe('Happy flow', function() { + it('expect return an array with 1 webhook', async function() { + sequelizeGetStub.resolves([webhookRaw]); + const webhooks = await sequelizeConnector.getAllWebhooks(); + + expect(sequelizeGetStub.calledOnce).to.be.equal(true); + expect(sequelizeGetStub.args[0][0]).to.be.deep.equal({ include: ['events'] }); + + expect(webhooks).to.be.an('array').and.have.lengthOf(1); + expect(webhooks[0]).to.be.deep.equal(webhookRaw); + }); + }); + }); +}); From ee138aa470cbfb17fdcf884d8ecad72004b7228d Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 22 Jun 2020 11:50:59 +0300 Subject: [PATCH 24/38] feat(webhooks): implement POST /webhooks (#319) --- .eslintrc.json | 2 +- docs/openapi3.yaml | 2 + package-lock.json | 526 +++++++++++++----- .../controllers/webhooksController.js | 10 + .../database/cassandra/cassandraConnector.js | 5 + .../models/database/databaseConnector.js | 5 + .../database/sequelize/sequelizeConnector.js | 38 +- src/webhooks/models/webhookManager.js | 12 + src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 41 ++ .../webhooks/webhooks-test.js | 142 +++++ .../jobs/sequelize/sequelizeConnector-test.js | 6 +- .../sequelize/sequelizeConnector-test.js | 60 +- 13 files changed, 703 insertions(+), 147 deletions(-) create mode 100644 tests/integration-tests/webhooks/helpers/requestCreator.js create mode 100644 tests/integration-tests/webhooks/webhooks-test.js diff --git a/.eslintrc.json b/.eslintrc.json index 6f95b6671..4c0ddf4ef 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 8, + "ecmaVersion": 2018, "sourceType": "module" }, "rules": { diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index f71549fe7..69b134056 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -2529,6 +2529,8 @@ components: events: description: list of events which will trigger the webhook type: array + uniqueItems: true + minItems: 1 items: $ref: '#/components/schemas/webhooks_types' format_type: diff --git a/package-lock.json b/package-lock.json index 4d72c0ab0..49f7f648a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ }, "@babel/generator": { "version": "7.9.4", - "resolved": "http://npm.zooz.co:8083/@babel%2fgenerator/-/generator-7.9.4.tgz", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { @@ -101,7 +101,7 @@ }, "@babel/helper-validator-identifier": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", "dev": true }, @@ -149,7 +149,7 @@ }, "@babel/parser": { "version": "7.9.4", - "resolved": "http://npm.zooz.co:8083/@babel%2fparser/-/parser-7.9.4.tgz", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", "dev": true }, @@ -172,7 +172,7 @@ }, "@babel/template": { "version": "7.8.6", - "resolved": "http://npm.zooz.co:8083/@babel%2ftemplate/-/template-7.8.6.tgz", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { @@ -183,7 +183,7 @@ }, "@babel/traverse": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2ftraverse/-/traverse-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { @@ -217,7 +217,7 @@ }, "@babel/types": { "version": "7.9.0", - "resolved": "http://npm.zooz.co:8083/@babel%2ftypes/-/types-7.9.0.tgz", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { @@ -745,9 +745,9 @@ } }, "acorn": { - "version": "7.1.1", - "resolved": "http://npm.zooz.co:8083/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", "dev": true }, "acorn-jsx": { @@ -821,7 +821,7 @@ }, "ansi-colors": { "version": "3.2.3", - "resolved": "http://npm.zooz.co:8083/ansi-colors/-/ansi-colors-3.2.3.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, @@ -864,7 +864,7 @@ }, "anymatch": { "version": "3.1.1", - "resolved": "http://npm.zooz.co:8083/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { @@ -874,7 +874,7 @@ "dependencies": { "normalize-path": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1363,7 +1363,7 @@ }, "binary-extensions": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/binary-extensions/-/binary-extensions-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, @@ -1511,7 +1511,7 @@ }, "braces": { "version": "3.0.2", - "resolved": "http://npm.zooz.co:8083/braces/-/braces-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { @@ -1825,7 +1825,7 @@ }, "chokidar": { "version": "3.3.0", - "resolved": "http://npm.zooz.co:8083/chokidar/-/chokidar-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { @@ -1841,13 +1841,13 @@ "dependencies": { "is-extglob": { "version": "2.1.1", - "resolved": "http://npm.zooz.co:8083/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-glob": { "version": "4.0.1", - "resolved": "http://npm.zooz.co:8083/is-glob/-/is-glob-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { @@ -1856,7 +1856,7 @@ }, "normalize-path": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1914,9 +1914,9 @@ "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==" }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, "cliui": { @@ -4242,22 +4242,22 @@ } }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz", + "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", @@ -4270,67 +4270,71 @@ "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", + "levn": "^0.4.1", "lodash": "^4.17.14", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.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", "v8-compile-cache": "^2.0.3" }, "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==", + "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": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "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": { - "color-convert": "^1.9.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, + "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 + }, "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": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "debug": { @@ -4342,6 +4346,27 @@ "ms": "^2.1.1" } }, + "eslint-utils": { + "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.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", + "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 + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4357,19 +4382,76 @@ "is-extglob": "^2.1.1" } }, + "levn": { + "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.2.1", + "type-check": "~0.4.0" + } + }, + "optionator": { + "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.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "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": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "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==", + "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-json-comments": { @@ -4379,12 +4461,30 @@ "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" + } + }, + "type-check": { + "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.2.1" + } + }, + "which": { + "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" } } } @@ -4604,9 +4704,9 @@ "dev": true }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4629,14 +4729,22 @@ "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", + "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", "dev": true, "requires": { - "acorn": "^7.1.1", + "acorn": "^7.2.0", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^1.2.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", + "dev": true + } } }, "esprima": { @@ -4645,18 +4753,18 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", - "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", + "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": "^5.0.0" + "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", - "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", "dev": true } } @@ -5026,7 +5134,7 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "http://npm.zooz.co:8083/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { @@ -5351,7 +5459,7 @@ }, "fsevents": { "version": "2.1.2", - "resolved": "http://npm.zooz.co:8083/fsevents/-/fsevents-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true @@ -6114,7 +6222,7 @@ }, "he": { "version": "1.2.0", - "resolved": "http://npm.zooz.co:8083/he/-/he-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, @@ -6385,9 +6493,9 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.2.0.tgz", + "integrity": "sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -6573,7 +6681,7 @@ }, "is-binary-path": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { @@ -6666,7 +6774,7 @@ }, "is-finite": { "version": "1.1.0", - "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", "dev": true }, @@ -6707,7 +6815,7 @@ }, "is-number": { "version": "7.0.0", - "resolved": "http://npm.zooz.co:8083/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, @@ -7168,7 +7276,7 @@ }, "kind-of": { "version": "6.0.3", - "resolved": "http://npm.zooz.co:8083/kind-of/-/kind-of-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "latest-version": { @@ -7849,7 +7957,7 @@ }, "minimist": { "version": "1.2.5", - "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.5.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { @@ -7931,7 +8039,7 @@ }, "mocha": { "version": "7.1.1", - "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-7.1.1.tgz", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { @@ -7963,7 +8071,7 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", - "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -7972,7 +8080,7 @@ }, "chalk": { "version": "2.4.2", - "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-2.4.2.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -7983,7 +8091,7 @@ "dependencies": { "supports-color": { "version": "5.5.0", - "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-5.5.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -7994,7 +8102,7 @@ }, "debug": { "version": "3.2.6", - "resolved": "http://npm.zooz.co:8083/debug/-/debug-3.2.6.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { @@ -8003,7 +8111,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "http://npm.zooz.co:8083/glob/-/glob-7.1.3.tgz", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { @@ -8017,7 +8125,7 @@ }, "log-symbols": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/log-symbols/-/log-symbols-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { @@ -8026,7 +8134,7 @@ }, "mkdirp": { "version": "0.5.3", - "resolved": "http://npm.zooz.co:8083/mkdirp/-/mkdirp-0.5.3.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { @@ -8035,13 +8143,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "http://npm.zooz.co:8083/ms/-/ms-2.1.1.tgz", + "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": "http://npm.zooz.co:8083/supports-color/-/supports-color-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { @@ -8460,7 +8568,7 @@ }, "node-environment-flags": { "version": "1.0.6", - "resolved": "http://npm.zooz.co:8083/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { @@ -8772,7 +8880,7 @@ }, "object.getownpropertydescriptors": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { @@ -9172,7 +9280,7 @@ }, "picomatch": { "version": "2.2.2", - "resolved": "http://npm.zooz.co:8083/picomatch/-/picomatch-2.2.2.tgz", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, @@ -9572,7 +9680,7 @@ }, "readdirp": { "version": "3.2.0", - "resolved": "http://npm.zooz.co:8083/readdirp/-/readdirp-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { @@ -10000,11 +10108,170 @@ }, "rewire": { "version": "5.0.0", - "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { "eslint": "^6.8.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 + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "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": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "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" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.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.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "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-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" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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" + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "rimraf": { @@ -10017,13 +10284,10 @@ } }, "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true }, "run-node": { "version": "1.0.0", @@ -11613,7 +11877,7 @@ }, "to-regex-range": { "version": "5.0.1", - "resolved": "http://npm.zooz.co:8083/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { @@ -11918,9 +12182,9 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "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 }, "valid-url": { @@ -12212,7 +12476,7 @@ }, "yargs-unparser": { "version": "1.6.0", - "resolved": "http://npm.zooz.co:8083/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index aa068ad11..cae0a4cf7 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -10,3 +10,13 @@ module.exports.getAllWebhooks = async function (req, res, next) { return next(err); } }; + +module.exports.createWebhook = async function (req, res, next) { + let webhook; + try { + webhook = await webhookManager.createWebhook(req.body); + return res.status(201).json(webhook); + } catch (err) { + return next(err); + } +}; \ No newline at end of file diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index b22b54435..432a77fd3 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -2,6 +2,7 @@ let logger = require('../../../../common/logger'); module.exports = { init, + createWebhook, getAllWebhooks }; @@ -14,3 +15,7 @@ async function init() { async function getAllWebhooks() { throw new Error('Not implemented.'); } + +async function createWebhook() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index de1c28e3f..27e1949d9 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -5,6 +5,7 @@ let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cass module.exports = { init, getAllWebhooks, + createWebhook, closeConnection }; @@ -18,4 +19,8 @@ function closeConnection() { async function getAllWebhooks(from, limit, exclude) { return databaseConnector.getAllWebhooks(from, limit, exclude); +} + +async function createWebhook(webhook) { + return databaseConnector.createWebhook(webhook); } \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 0045016b4..1e10da838 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -1,11 +1,21 @@ 'use strict'; const Sequelize = require('sequelize'); +const uuid = require('uuid'); + let client; module.exports = { init, - getAllWebhooks + getAllWebhooks, + createWebhook +}; + +function parseWebhook(webhookRecord) { + return { + ...webhookRecord.dataValues, + events: webhookRecord.events && webhookRecord.events.map(eventRecord => eventRecord.dataValues.name) + }; }; async function init(sequelizeClient) { @@ -15,7 +25,31 @@ async function init(sequelizeClient) { async function getAllWebhooks() { const webhooksModel = client.model('webhook'); - return webhooksModel.findAll({ include: ['events'] }); + const webhooks = await webhooksModel.findAll({ include: ['events'] }); + return webhooks.map(parseWebhook); +} + +async function createWebhook(webhook) { + const id = uuid.v4(); + const webhooksModel = client.model('webhook'); + const webhooksEvents = client.model('webhook_event'); + const events = await webhooksEvents.findAll({ where: { name: webhook.events } }); + const eventsIds = events.map(({ id }) => id); + const webhookToInsert = { + id, + name: webhook.name, + url: webhook.url, + format_type: webhook.format_type, + global: webhook.global + }; + await client.transaction(async function(transaction) { + const createdWebhook = await webhooksModel.create(webhookToInsert, { transaction }); + await createdWebhook.setEvents(eventsIds, { transaction }); + return createdWebhook; + }); + const retrievedWebhook = await webhooksModel.findByPk(id, { include: ['events'] }); + const parsedWebhook = parseWebhook(retrievedWebhook); + return parsedWebhook; } async function initSchemas() { diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 0fbe56dd9..4d0133f02 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -2,7 +2,19 @@ const databaseConnector = require('./database/databaseConnector'); +const webhookDefaultValues = { + global: false +}; + module.exports.getAllWebhooks = async function () { let getAllWebhooks = await databaseConnector.getAllWebhooks(); return getAllWebhooks; }; + +module.exports.createWebhook = async function(webhookInfo) { + const webhook = { + ...webhookDefaultValues, + ...webhookInfo + }; + return databaseConnector.createWebhook(webhook); +}; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 9a5a24ec4..a9e59ab1e 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -7,5 +7,6 @@ let router = express.Router(); let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); +router.post('/', swaggerValidator.validate, webhooksController.createWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js new file mode 100644 index 000000000..0db7b19b5 --- /dev/null +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -0,0 +1,41 @@ + +const request = require('supertest'); +const expressApp = require('../../../../src/app'); + +let app; +const headers = { 'Content-Type': 'application/json' }; +const resourceUri = '/v1/webhooks'; + +module.exports = { + init, + createWebhook, + getWebhooks +}; + +async function init() { + try { + app = await expressApp(); + } catch (err){ + console.log(err); + process.exit(1); + } +} + +function createWebhook(body) { + return request(app) + .post(resourceUri) + .send(body) + .set(headers) + .expect(function(res){ + return res; + }); +} + +function getWebhooks() { + return request(app) + .get(resourceUri) + .set(headers) + .expect(function (res) { + return res; + }); +} \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js new file mode 100644 index 000000000..eb13793a9 --- /dev/null +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -0,0 +1,142 @@ +const { expect } = require('chai'); +const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); + +const webhookRequestSender = require('./helpers/requestCreator'); + +describe('Webhooks api', function () { + this.timeout(5000000); + before(async function () { + await webhookRequestSender.init(); + }); + + describe('Good requests', async function () { + describe('GET /v1/webhooks', async function () { + const numOfWebhooksToInsert = 5; + it(`return ${numOfWebhooksToInsert} webhooks`, async function() { + const webhooksToInsert = (new Array(numOfWebhooksToInsert)) + .fill(0, 0, numOfWebhooksToInsert) + .map(index => generateWebhook(`My webhook ${index}`)); + await Promise.all(webhooksToInsert.map(webhook => webhookRequestSender.createWebhook(webhook))); + + const webhooksGetResponse = await webhookRequestSender.getWebhooks(); + expect(webhooksGetResponse.statusCode).to.equal(200); + + const webhooks = webhooksGetResponse.body; + expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); + }); + }); + describe('POST /v1/webhooks', function () { + it('Create webhook and response 201 status code', async function() { + const webhook = generateWebhook(); + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + it('Create webhook with global=true and response 201 status code', async function() { + const webhook = generateWebhook(); + webhook.global = true; + + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + it('Create webhook with unspecified global field, expect default value and response 201 status code', async function() { + const webhook = generateWebhook(); + const { global, ...webhookWithoutGlobal } = webhook; + + let createWebhookResponse = await webhookRequestSender.createWebhook(webhookWithoutGlobal); + + expect(createWebhookResponse.statusCode).to.equal(201); + assertDeepWebhookEquality(webhook, createWebhookResponse.body); + }); + }); + }); + + describe('Bad requests', function () { + describe('POST /v1/webhooks', function () { + describe('name validation', function() { + it('Create webhook with bad type of name', async function () { + const webhook = generateWebhook(5); + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('format_type validation', function() { + it('Create webhook with bad format_type', async function () { + const webhook = generateWebhook(); + webhook.format_type = 'TOTTALLY NOT A VALID FORMAT TYPE lalalalalla'; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('global validation', function() { + it('Create webhook with global not a boolean', async function () { + const webhook = generateWebhook(); + webhook.global = 'TOTTALLY NOT A VALID GLOBAL'; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + describe('events validation', function() { + it('Create webhook with empty events', async function () { + const webhook = generateWebhook(); + webhook.events = []; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with invalid event name', async function () { + const webhook = generateWebhook('My special webhook', 'https://url.com/callback', ['bad_value']); + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with duplicate event name', async function () { + const webhook = generateWebhook(); + webhook.events = [webhook.events[0], webhook.events[0]]; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + it('Create a webhook with too many valid values', async function () { + const webhook = generateWebhook(); + webhook.events = [...webhook.events, webhook.events[0]]; + + const createResponse = await webhookRequestSender.createWebhook(webhook); + expect(createResponse.statusCode).to.equal(400); + }); + }); + }); + }); +}); + +function generateWebhook(name = 'My webhook', url = 'https://humus.is.love/callback', events = WEBHOOK_EVENT_TYPES) { + return { + name, + url, + events, + global: false, + format_type: EVENT_FORMAT_TYPE_JSON + }; +} + +function assertDeepWebhookEquality(webhook, webhookFromAPI) { + const { + id, + events, + ...restOfWebhook + } = webhook; + const { + id: webhookFromAPIId, + events: webhookFromAPIEvents, + created_at: createdAt, + updated_at: updatedAt, + ...restwebhookFromAPI + } = webhookFromAPI; + expect(restOfWebhook).to.deep.equal(restwebhookFromAPI); + expect(events).to.have.members(webhookFromAPIEvents); +} \ No newline at end of file diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index 16b5b1c0e..a70371998 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -45,12 +45,14 @@ describe('Sequelize client tests', function () { hasMany: () => { }, sync: () => { - } + }, + belongsToMany: () => {} }); sequelizeModelStub.returns({ email: {}, webhook: {}, + belongsToMany: () => {}, create: sequelizeCreateStub, update: sequelizeUpdateStub, findAll: sequelizeGetStub, @@ -398,7 +400,7 @@ describe('Sequelize client tests', function () { }, 'include': [ {}, - {} + 'webhooks' ], 'where': { 'id': 'd6b0f076-2efb-48e1-82d2-82250818f59c' diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js index f0adad6a9..028e2d144 100644 --- a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -4,15 +4,20 @@ const sinon = require('sinon'), databaseConfig = require('../../../../src/config/databaseConfig'), sequelizeConnector = require('../../../../src/webhooks/models/database/sequelize/sequelizeConnector'); +const { WEBHOOK_EVENT_TYPES } = require('../../../../src/common/consts'); +const uuid = require('uuid'); + describe('Sequelize client tests', function () { const webhookRaw = { - id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', - name: 'my special webhook', - url: 'http://callback.com', - global: false, - format_type: 'json', - created_at: '2020-06-13T13:13:16.763Z', - updated_at: '2020-06-13T13:13:16.763Z', + dataValues: { + id: '3e10d10e-2ae0-418e-aa91-0fd659dd86fb', + name: 'my special webhook', + url: 'http://callback.com', + global: false, + format_type: 'json', + created_at: '2020-06-13T13:13:16.763Z', + updated_at: '2020-06-13T13:13:16.763Z', + }, events: [] }; @@ -24,7 +29,8 @@ describe('Sequelize client tests', function () { sequelizeGetStub, sequelizeCreateStub, sequelizeUpdateStub, - sequelizeBelongsToMany; + sequelizeBelongsToMany, + sequelizeTransactionStub; before(async () => { sandbox = sinon.sandbox.create(); @@ -44,6 +50,7 @@ describe('Sequelize client tests', function () { sequelizeCreateStub = sandbox.stub(); sequelizeUpdateStub = sandbox.stub(); sequelizeBelongsToMany = sandbox.stub(); + sequelizeTransactionStub = sandbox.stub(); sequelizeDefineStub.returns({ hasMany: () => { @@ -61,12 +68,14 @@ describe('Sequelize client tests', function () { findAll: sequelizeGetStub, findOne: sequelizeGeValueStub, destroy: sequelizeDeleteStub, - create: sequelizeCreateStub + create: sequelizeCreateStub, + findByPk: sequelizeGetStub }); await sequelizeConnector.init({ model: sequelizeModelStub, - define: sequelizeDefineStub + define: sequelizeDefineStub, + transaction: sequelizeTransactionStub }); }); @@ -88,7 +97,36 @@ describe('Sequelize client tests', function () { expect(sequelizeGetStub.args[0][0]).to.be.deep.equal({ include: ['events'] }); expect(webhooks).to.be.an('array').and.have.lengthOf(1); - expect(webhooks[0]).to.be.deep.equal(webhookRaw); + expect(webhooks[0]).to.be.deep.contain(webhookRaw.dataValues); + expect(webhooks[0].events).to.be.deep.equal(webhookRaw.events); + }); + }); + }); + + describe('createWebhook', function() { + describe('Happy flow', function() { + it('expect to return a webhook with a valid uuid', async function() { + const events = [WEBHOOK_EVENT_TYPES[0], WEBHOOK_EVENT_TYPES[1]]; + const eventRecords = events.map(event => ({dataValues: {id: uuid(), name: event}})); + const webhook = { + ...webhookRaw.dataValues, + events + }; + const webhookWithEvents = { + dataValues: { + ...webhookRaw.dataValues + }, + events: eventRecords + }; + sequelizeGetStub.onFirstCall().resolves(eventRecords); + sequelizeGetStub.onSecondCall().resolves(webhookWithEvents); + sequelizeTransactionStub.resolves(); + const createdWebhook = await sequelizeConnector.createWebhook(webhook); + + expect(createdWebhook).to.be.an('object'); + expect(createdWebhook).to.be.deep.contain(webhookWithEvents.dataValues); + expect(createdWebhook.events).to.be.deep.equal(events); + expect(createdWebhook).to.have.a.property('id'); }); }); }); From a2f765a65c305746ea2fa2ede6f1e4d7ebae5193 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 22 Jun 2020 12:44:29 +0300 Subject: [PATCH 25/38] implement GET /webhooks/:webhook_id (#320) --- docs/openapi3.yaml | 18 ++++++++++ .../controllers/webhooksController.js | 11 ++++++ .../models/database/databaseConnector.js | 6 ++++ .../database/sequelize/sequelizeConnector.js | 11 ++++-- src/webhooks/models/webhookManager.js | 11 ++++++ src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 11 ++++++ .../webhooks/webhooks-test.js | 35 +++++++++++++++++++ 8 files changed, 101 insertions(+), 3 deletions(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 69b134056..48222111f 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -1454,6 +1454,15 @@ paths: - Webhooks summary: Retrieve a webhook by id description: Retrieve a webhook by id. + parameters: + - in: path + name: webhook_id + description: The webhook id. + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 responses: '200': description: Success @@ -1479,6 +1488,15 @@ paths: - Webhooks summary: Update a webhook description: Update a webhook. + parameters: + - in: path + name: webhook_id + description: The webhook id. + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 responses: '200': description: Success diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index cae0a4cf7..7d7d8d323 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -11,6 +11,17 @@ module.exports.getAllWebhooks = async function (req, res, next) { } }; +module.exports.getWebhook = async function (req, res, next) { + let webhook; + let webhookId = req.params.webhook_id; + try { + webhook = await webhookManager.getWebhook(webhookId); + return res.status(200).json(webhook); + } catch (err) { + return next(err); + } +}; + module.exports.createWebhook = async function (req, res, next) { let webhook; try { diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 27e1949d9..3bae86155 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -2,10 +2,12 @@ let databaseConfig = require('../../../config/databaseConfig'); let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; + module.exports = { init, getAllWebhooks, createWebhook, + getWebhook, closeConnection }; @@ -23,4 +25,8 @@ async function getAllWebhooks(from, limit, exclude) { async function createWebhook(webhook) { return databaseConnector.createWebhook(webhook); +} + +async function getWebhook(webhookId) { + return databaseConnector.getWebhook(webhookId); } \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index 1e10da838..e41678e34 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -1,5 +1,3 @@ -'use strict'; - const Sequelize = require('sequelize'); const uuid = require('uuid'); @@ -8,7 +6,8 @@ let client; module.exports = { init, getAllWebhooks, - createWebhook + createWebhook, + getWebhook }; function parseWebhook(webhookRecord) { @@ -29,6 +28,12 @@ async function getAllWebhooks() { return webhooks.map(parseWebhook); } +async function getWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + const webhook = await webhooksModel.findByPk(webhookId, { include: ['events'] }); + return parseWebhook(webhook); +} + async function createWebhook(webhook) { const id = uuid.v4(); const webhooksModel = client.model('webhook'); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 4d0133f02..6dc4bfa65 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -1,6 +1,7 @@ 'use strict'; const databaseConnector = require('./database/databaseConnector'); +const { ERROR_MESSAGES } = require('../../common/consts'); const webhookDefaultValues = { global: false @@ -11,6 +12,16 @@ module.exports.getAllWebhooks = async function () { return getAllWebhooks; }; +module.exports.getWebhook = async function (webhookId) { + const webhook = await databaseConnector.getWebhook(webhookId); + if (!webhook) { + const error = new Error(ERROR_MESSAGES.NOT_FOUND); + error.statusCode = 404; + throw error; + } + return webhook; +}; + module.exports.createWebhook = async function(webhookInfo) { const webhook = { ...webhookDefaultValues, diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index a9e59ab1e..79f23d48f 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -8,5 +8,6 @@ let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); +router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index 0db7b19b5..7c3330527 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -9,6 +9,8 @@ const resourceUri = '/v1/webhooks'; module.exports = { init, createWebhook, + getWebhooks, + getWebhook getWebhooks }; @@ -38,4 +40,13 @@ function getWebhooks() { .expect(function (res) { return res; }); +} + +function getWebhook(webhookdId) { + return request(app) + .get(`${resourceUri}/${webhookdId}`) + .set(headers) + .expect(function (res) { + return res; + }); } \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index eb13793a9..bc290f25c 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -1,4 +1,6 @@ const { expect } = require('chai'); +const uuid = require('uuid'); + const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); const webhookRequestSender = require('./helpers/requestCreator'); @@ -25,6 +27,19 @@ describe('Webhooks api', function () { expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); }); }); + describe('GET /webhook/:webhook_id', function () { + it('should retrieve the webhook that was created', async function() { + const webhook = generateWebhook(); + let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(createWebhookResponse.statusCode).to.equal(201); + + const webhookId = createWebhookResponse.body.id; + const getWebhookResponse = await webhookRequestSender.getWebhook(webhookId); + + expect(getWebhookResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(webhook, getWebhookResponse.body); + }); + }); describe('POST /v1/webhooks', function () { it('Create webhook and response 201 status code', async function() { const webhook = generateWebhook(); @@ -111,6 +126,26 @@ describe('Webhooks api', function () { }); }); }); + describe('GET /webhook/:webhook_id', function () { + it('should return 400 for bad uuid', async function() { + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.getWebhook(badWebhookValue); + + expect(response.statusCode).to.equal(400); + }); + }); + }); + describe('Sad requests', function() { + describe('GET /webhook/:webhook_id', function () { + it('should return 404 for no existing webhook', async function() { + const notExistingWebhookId = uuid.v4(); + + const response = await webhookRequestSender.getWebhook(notExistingWebhookId); + + expect(response.statusCode).to.equal(404); + }); + }); }); }); From a26ed09226f02691af5338c0f06f98ccb20ddd31 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Mon, 29 Jun 2020 20:49:57 +0300 Subject: [PATCH 26/38] feat(webhooks): implement DELETE /webhooks/:webhook_id (#325) --- docs/openapi3.yaml | 31 +++++++++++++++- .../database/sequelize/sequelizeConnector.js | 3 +- .../controllers/webhooksController.js | 10 +++++ .../models/database/databaseConnector.js | 7 +++- .../database/sequelize/sequelizeConnector.js | 17 +++++++-- src/webhooks/models/webhookManager.js | 4 ++ src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 18 ++++++--- .../webhooks/webhooks-test.js | 37 +++++++++++++++++-- .../sequelize/sequelizeConnector-test.js | 16 ++++++++ 10 files changed, 129 insertions(+), 15 deletions(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 48222111f..7beb2b003 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -1522,6 +1522,35 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + delete: + operationId: delete-webhook + tags: + - Webhooks + summary: Delete webhook file + description: Delete a specific webhook by id. + parameters: + - in: path + name: webhook_id + required: true + schema: + type: string + format: uuid + example: 4bf5d7ab-f310-4a64-8ec2-d65c06188ec1 + responses: + '204': + description: Success + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' # Files /v1/files: @@ -2523,8 +2552,6 @@ components: filename: type: string description: the name of the file - type: number - description: benchmark percentage weight webhook: type: object required: diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index c56c18c55..7ac38a393 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -179,7 +179,8 @@ async function initSchemas() { webhooks.belongsToMany(job, { through: 'webhook_job_mapping', as: 'jobs', - foreignKey: 'webhook_id' + foreignKey: 'webhook_id', + onDelete: 'CASCADE' }); job.belongsToMany(webhooks, { through: 'webhook_job_mapping', diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index 7d7d8d323..a957c8349 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -30,4 +30,14 @@ module.exports.createWebhook = async function (req, res, next) { } catch (err) { return next(err); } +}; + +module.exports.deleteWebhook = async function (req, res, next) { + let webhookId = req.params.webhook_id; + try { + await webhookManager.deleteWebhook(webhookId); + return res.status(204).json(); + } catch (err) { + return next(err); + } }; \ No newline at end of file diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index 3bae86155..b63c53018 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -8,6 +8,7 @@ module.exports = { getAllWebhooks, createWebhook, getWebhook, + deleteWebhook, closeConnection }; @@ -29,4 +30,8 @@ async function createWebhook(webhook) { async function getWebhook(webhookId) { return databaseConnector.getWebhook(webhookId); -} \ No newline at end of file +} + +async function deleteWebhook(webhookId) { + return databaseConnector.deleteWebhook(webhookId); +}; \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index e41678e34..f526c9137 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -7,11 +7,12 @@ module.exports = { init, getAllWebhooks, createWebhook, - getWebhook + getWebhook, + deleteWebhook }; function parseWebhook(webhookRecord) { - return { + return webhookRecord && { ...webhookRecord.dataValues, events: webhookRecord.events && webhookRecord.events.map(eventRecord => eventRecord.dataValues.name) }; @@ -57,6 +58,15 @@ async function createWebhook(webhook) { return parsedWebhook; } +async function deleteWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + return webhooksModel.destroy({ + where: { + id: webhookId + } + }); +} + async function initSchemas() { const webhooksSchema = client.define('webhook', { id: { @@ -95,7 +105,8 @@ async function initSchemas() { webhooksSchema.belongsToMany(webhooksEvents, { through: 'webhook_event_mapping', as: 'events', - foreignKey: 'webhook_id' + foreignKey: 'webhook_id', + onDelete: 'CASCADE' }); webhooksEvents.belongsToMany(webhooksSchema, { through: 'webhook_event_mapping', diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 6dc4bfa65..2be6f9986 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -28,4 +28,8 @@ module.exports.createWebhook = async function(webhookInfo) { ...webhookInfo }; return databaseConnector.createWebhook(webhook); +}; + +module.exports.deleteWebhook = async function(webhookId) { + return databaseConnector.deleteWebhook(webhookId); }; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 79f23d48f..1e53ae499 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -9,5 +9,6 @@ let webhooksController = require('../controllers/webhooksController'); router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); +router.delete('/:webhook_id', swaggerValidator.validate, webhooksController.deleteWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index 7c3330527..ae3d53ba0 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -1,4 +1,3 @@ - const request = require('supertest'); const expressApp = require('../../../../src/app'); @@ -10,8 +9,8 @@ module.exports = { init, createWebhook, getWebhooks, - getWebhook - getWebhooks + getWebhook, + deleteWebhook }; async function init() { @@ -42,9 +41,18 @@ function getWebhooks() { }); } -function getWebhook(webhookdId) { +function getWebhook(webhookId) { + return request(app) + .get(`${resourceUri}/${webhookId}`) + .set(headers) + .expect(function (res) { + return res; + }); +} + +function deleteWebhook(webhookId) { return request(app) - .get(`${resourceUri}/${webhookdId}`) + .delete(`${resourceUri}/${webhookId}`) .set(headers) .expect(function (res) { return res; diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index bc290f25c..f2df0f9f3 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -27,7 +27,7 @@ describe('Webhooks api', function () { expect(webhooks).to.be.an('array').and.have.lengthOf(numOfWebhooksToInsert); }); }); - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should retrieve the webhook that was created', async function() { const webhook = generateWebhook(); let createWebhookResponse = await webhookRequestSender.createWebhook(webhook); @@ -66,6 +66,27 @@ describe('Webhooks api', function () { assertDeepWebhookEquality(webhook, createWebhookResponse.body); }); }); + describe('DELETE /v1/webhooks/:webhook_id', function() { + it('Create a webhook -> deleting it -> expecting to get 404 in GET', async function() { + const webhook = generateWebhook(); + + const insertWebhookResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertWebhookResponse.statusCode).to.equal(201); + const webhookId = insertWebhookResponse.body.id; + + const deleteWebhookResponse = await webhookRequestSender.deleteWebhook(webhookId); + expect(deleteWebhookResponse.statusCode).to.equal(204); + + const getWebhookResponse = await webhookRequestSender.getWebhook(webhookId); + expect(getWebhookResponse.statusCode).to.equal(404); + }); + it('Deleting unexisting webhook -> expect 204 status code', async function() { + const id = uuid.v4(); + + const deleteWebhookResponse = await webhookRequestSender.deleteWebhook(id); + expect(deleteWebhookResponse.statusCode).to.equal(204); + }); + }); }); describe('Bad requests', function () { @@ -126,18 +147,28 @@ describe('Webhooks api', function () { }); }); }); - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should return 400 for bad uuid', async function() { const badWebhookValue = 'lalallalalal'; const response = await webhookRequestSender.getWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); + }); + }); + describe('DELETE /v1/webhooks/:webhook_id', function () { + it('should return 400 for bad uuid', async function() { + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.deleteWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); }); }); }); + describe('Sad requests', function() { - describe('GET /webhook/:webhook_id', function () { + describe('GET /v1/webhooks/:webhook_id', function () { it('should return 404 for no existing webhook', async function() { const notExistingWebhookId = uuid.v4(); diff --git a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js index 028e2d144..5c5c4b004 100644 --- a/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/webhooks/sequelize/sequelizeConnector-test.js @@ -130,4 +130,20 @@ describe('Sequelize client tests', function () { }); }); }); + describe('deleteWebhook', function() { + describe('Happy flow', function() { + it('expect to delete by query with proper webhook_id', async function() { + const id = uuid.v4(); + const queryOptions = { where: { id } }; + + sequelizeDeleteStub.resolves(); + await sequelizeConnector.deleteWebhook(id); + + expect(sequelizeDeleteStub.calledOnce).to.equal(true); + + const destroyOptions = sequelizeDeleteStub.args[0][0]; + expect(destroyOptions).to.deep.equal(queryOptions); + }); + }); + }); }); From 692aee6854608734ec015aa7df8ba71c5051815f Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Sun, 5 Jul 2020 13:22:53 +0300 Subject: [PATCH 27/38] feat(webhooks): implement PUT /webhooks/:webhook_id --- src/common/generateError.js | 6 + src/processors/models/processorsManager.js | 23 ++-- .../controllers/webhooksController.js | 10 ++ .../database/cassandra/cassandraConnector.js | 7 +- .../models/database/databaseConnector.js | 5 + .../database/sequelize/sequelizeConnector.js | 24 +++- src/webhooks/models/webhookManager.js | 29 +++-- src/webhooks/routes/webhooksRouter.js | 1 + .../webhooks/helpers/requestCreator.js | 13 +- .../webhooks/webhooks-test.js | 111 +++++++++++++++++- 10 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 src/common/generateError.js diff --git a/src/common/generateError.js b/src/common/generateError.js new file mode 100644 index 000000000..411215b7e --- /dev/null +++ b/src/common/generateError.js @@ -0,0 +1,6 @@ + +module.exports = function(stautsCode, message) { + const error = new Error(message); + error.statusCode = stautsCode; + return error; +}; \ No newline at end of file diff --git a/src/processors/models/processorsManager.js b/src/processors/models/processorsManager.js index f5a853b52..2bbecec5b 100644 --- a/src/processors/models/processorsManager.js +++ b/src/processors/models/processorsManager.js @@ -5,12 +5,13 @@ const uuid = require('uuid'); const logger = require('../../common/logger'), databaseConnector = require('./database/databaseConnector'), testsManager = require('../../tests/models/manager'), - { ERROR_MESSAGES } = require('../../common/consts'); + { ERROR_MESSAGES } = require('../../common/consts'), + generateError = require('../../common/generateError'); module.exports.createProcessor = async function (processor) { const processorWithTheSameName = await databaseConnector.getProcessorByName(processor.name); if (processorWithTheSameName) { - throw generateError(ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST, 400); + throw generateError(400, ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST); } let processorId = uuid.v4(); try { @@ -36,7 +37,7 @@ module.exports.getProcessor = async function (processorId) { if (processor) { return processor; } else { - const error = generateError(ERROR_MESSAGES.NOT_FOUND, 404); + const error = generateError(404, ERROR_MESSAGES.NOT_FOUND); throw error; } }; @@ -46,7 +47,7 @@ module.exports.deleteProcessor = async function (processorId) { if (tests.length > 0) { let testNames = tests.map(test => test.name); let message = `${ERROR_MESSAGES.PROCESSOR_DELETION_FORBIDDEN}: ${testNames.join(', ')}`; - throw generateError(message, 409); + throw generateError(409, message); } return databaseConnector.deleteProcessor(processorId); }; @@ -54,12 +55,12 @@ module.exports.deleteProcessor = async function (processorId) { module.exports.updateProcessor = async function (processorId, processor) { const oldProcessor = await databaseConnector.getProcessorById(processorId); if (!oldProcessor) { - throw generateError(ERROR_MESSAGES.NOT_FOUND, 404); + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); } if (oldProcessor.name !== processor.name) { const processorWithUpdatedName = await databaseConnector.getProcessorByName(processor.name); if (processorWithUpdatedName) { - throw generateError(ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST, 400); + throw generateError(400, ERROR_MESSAGES.PROCESSOR_NAME_ALREADY_EXIST); } } @@ -79,19 +80,13 @@ function verifyJSAndGetExportedFunctions(src) { let exports = m.exports; exportedFunctions = Object.keys(exports); } catch (err) { - let error = generateError('javascript syntax validation failed with error: ' + err.message, 422); + let error = generateError(422, 'javascript syntax validation failed with error: ' + err.message); throw error; } if (exportedFunctions.length === 0) { - let error = generateError('javascript has 0 exported functions', 422); + let error = generateError(422, 'javascript has 0 exported functions'); throw error; } return exportedFunctions; } - -function generateError(message, statusCode) { - const error = new Error(message); - error.statusCode = statusCode; - return error; -} diff --git a/src/webhooks/controllers/webhooksController.js b/src/webhooks/controllers/webhooksController.js index a957c8349..0def59d2d 100644 --- a/src/webhooks/controllers/webhooksController.js +++ b/src/webhooks/controllers/webhooksController.js @@ -40,4 +40,14 @@ module.exports.deleteWebhook = async function (req, res, next) { } catch (err) { return next(err); } +}; + +module.exports.updateWebhook = async function (req, res, next) { + let { body: updatedWebhook, params: { webhook_id: webhookId } } = req; + try { + const webhook = await webhookManager.updateWebhook(webhookId, updatedWebhook); + return res.status(200).json(webhook); + } catch (err) { + return next(err); + } }; \ No newline at end of file diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js index 432a77fd3..844a2d71f 100644 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ b/src/webhooks/models/database/cassandra/cassandraConnector.js @@ -3,7 +3,8 @@ let logger = require('../../../../common/logger'); module.exports = { init, createWebhook, - getAllWebhooks + getAllWebhooks, + updateWebhook }; async function init() { @@ -19,3 +20,7 @@ async function getAllWebhooks() { async function createWebhook() { throw new Error('Not implemented.'); } + +async function updateWebhook() { + throw new Error('Not implemented.'); +} diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index b63c53018..94f4f17b7 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -9,6 +9,7 @@ module.exports = { createWebhook, getWebhook, deleteWebhook, + updateWebhook, closeConnection }; @@ -34,4 +35,8 @@ async function getWebhook(webhookId) { async function deleteWebhook(webhookId) { return databaseConnector.deleteWebhook(webhookId); +}; + +async function updateWebhook(webhookId, webhook) { + return databaseConnector.updateWebhook(webhookId, webhook); }; \ No newline at end of file diff --git a/src/webhooks/models/database/sequelize/sequelizeConnector.js b/src/webhooks/models/database/sequelize/sequelizeConnector.js index f526c9137..3ab471c20 100644 --- a/src/webhooks/models/database/sequelize/sequelizeConnector.js +++ b/src/webhooks/models/database/sequelize/sequelizeConnector.js @@ -8,6 +8,7 @@ module.exports = { getAllWebhooks, createWebhook, getWebhook, + updateWebhook, deleteWebhook }; @@ -18,6 +19,11 @@ function parseWebhook(webhookRecord) { }; }; +async function _getWebhook(webhookId) { + const webhooksModel = client.model('webhook'); + return webhooksModel.findByPk(webhookId, { include: ['events'] }); +} + async function init(sequelizeClient) { client = sequelizeClient; await initSchemas(); @@ -30,8 +36,7 @@ async function getAllWebhooks() { } async function getWebhook(webhookId) { - const webhooksModel = client.model('webhook'); - const webhook = await webhooksModel.findByPk(webhookId, { include: ['events'] }); + const webhook = await _getWebhook(webhookId); return parseWebhook(webhook); } @@ -67,6 +72,21 @@ async function deleteWebhook(webhookId) { }); } +async function updateWebhook(webhookId, updatedWebhook) { + const webhooksModel = client.model('webhook'); + const webhooksEvents = client.model('webhook_event'); + + const oldWebhook = await _getWebhook(webhookId); + const newWebhookEvents = await webhooksEvents.findAll({ where: { name: updatedWebhook.events } }); + const newWebhookEventsIds = newWebhookEvents.map(({ id }) => id); + + await client.transaction(async function(transaction) { + await oldWebhook.setEvents(newWebhookEventsIds, { transaction }); + return webhooksModel.update(updatedWebhook, { where: { id: webhookId }, transaction }); + }); + return getWebhook(webhookId); +} + async function initSchemas() { const webhooksSchema = client.define('webhook', { id: { diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index 2be6f9986..d40efad4c 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -2,27 +2,26 @@ const databaseConnector = require('./database/databaseConnector'); const { ERROR_MESSAGES } = require('../../common/consts'); +const generateError = require('../../common/generateError'); const webhookDefaultValues = { global: false }; -module.exports.getAllWebhooks = async function () { +async function getAllWebhooks() { let getAllWebhooks = await databaseConnector.getAllWebhooks(); return getAllWebhooks; }; -module.exports.getWebhook = async function (webhookId) { +async function getWebhook(webhookId) { const webhook = await databaseConnector.getWebhook(webhookId); if (!webhook) { - const error = new Error(ERROR_MESSAGES.NOT_FOUND); - error.statusCode = 404; - throw error; + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); } return webhook; }; -module.exports.createWebhook = async function(webhookInfo) { +async function createWebhook(webhookInfo) { const webhook = { ...webhookDefaultValues, ...webhookInfo @@ -30,6 +29,22 @@ module.exports.createWebhook = async function(webhookInfo) { return databaseConnector.createWebhook(webhook); }; -module.exports.deleteWebhook = async function(webhookId) { +async function deleteWebhook(webhookId) { return databaseConnector.deleteWebhook(webhookId); +}; + +async function updateWebhook(webhookId, webhook) { + const webhookInDB = await getWebhook(webhookId); + if (!webhookInDB) { + throw generateError(404, ERROR_MESSAGES.NOT_FOUND); + } + return databaseConnector.updateWebhook(webhookId, webhook); +}; + +module.exports = { + getAllWebhooks, + getWebhook, + createWebhook, + deleteWebhook, + updateWebhook }; \ No newline at end of file diff --git a/src/webhooks/routes/webhooksRouter.js b/src/webhooks/routes/webhooksRouter.js index 1e53ae499..098187c4c 100644 --- a/src/webhooks/routes/webhooksRouter.js +++ b/src/webhooks/routes/webhooksRouter.js @@ -10,5 +10,6 @@ router.get('/', swaggerValidator.validate, webhooksController.getAllWebhooks); router.post('/', swaggerValidator.validate, webhooksController.createWebhook); router.get('/:webhook_id', swaggerValidator.validate, webhooksController.getWebhook); router.delete('/:webhook_id', swaggerValidator.validate, webhooksController.deleteWebhook); +router.put('/:webhook_id', swaggerValidator.validate, webhooksController.updateWebhook); module.exports = router; diff --git a/tests/integration-tests/webhooks/helpers/requestCreator.js b/tests/integration-tests/webhooks/helpers/requestCreator.js index ae3d53ba0..383421aea 100644 --- a/tests/integration-tests/webhooks/helpers/requestCreator.js +++ b/tests/integration-tests/webhooks/helpers/requestCreator.js @@ -10,7 +10,8 @@ module.exports = { createWebhook, getWebhooks, getWebhook, - deleteWebhook + deleteWebhook, + updateWebhook }; async function init() { @@ -57,4 +58,14 @@ function deleteWebhook(webhookId) { .expect(function (res) { return res; }); +} + +function updateWebhook(webhookId, webhook) { + return request(app) + .put(`${resourceUri}/${webhookId}`) + .send(webhook) + .set(headers) + .expect(function (res) { + return res; + }); } \ No newline at end of file diff --git a/tests/integration-tests/webhooks/webhooks-test.js b/tests/integration-tests/webhooks/webhooks-test.js index f2df0f9f3..5e208637e 100644 --- a/tests/integration-tests/webhooks/webhooks-test.js +++ b/tests/integration-tests/webhooks/webhooks-test.js @@ -1,7 +1,7 @@ const { expect } = require('chai'); const uuid = require('uuid'); -const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'); +const { WEBHOOK_EVENT_TYPES, EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPES, WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED } = require('../../../src/common/consts'); const webhookRequestSender = require('./helpers/requestCreator'); @@ -87,6 +87,95 @@ describe('Webhooks api', function () { expect(deleteWebhookResponse.statusCode).to.equal(204); }); }); + describe('PUT /v1/webhooks/:webhook_id', function() { + it('Insert a webhook -> update global -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, global: !webhook.global }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update url -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, url: `${webhook}/new/path` }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update format_type -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedFormatType = EVENT_FORMAT_TYPES.filter(format => format !== webhook.format_type)[0]; + const updatedWebhook = { ...webhook, format_type: updatedFormatType }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update name -> ensure it is updated', async function() { + const webhook = generateWebhook(); + const updatedWebhook = { ...webhook, name: webhook.name + ' added text' }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + it('Insert a webhook -> update events -> ensure it is updated', async function() { + const webhook = { + ...generateWebhook(), + events: [WEBHOOK_EVENT_TYPE_API_FAILURE] + }; + const updatedWebhook = { + ...webhook, + events: [WEBHOOK_EVENT_TYPE_API_FAILURE, WEBHOOK_EVENT_TYPE_FAILED] + }; + + const insertResponse = await webhookRequestSender.createWebhook(webhook); + expect(insertResponse.statusCode).to.equal(201); + + const { body: { id } } = insertResponse; + const putResponse = await webhookRequestSender.updateWebhook(id, updatedWebhook); + + expect(putResponse.statusCode).to.equal(200); + assertDeepWebhookEquality(updatedWebhook, putResponse.body); + + const getResponse = await webhookRequestSender.getWebhook(id); + assertDeepWebhookEquality(updatedWebhook, getResponse.body); + }); + }); }); describe('Bad requests', function () { @@ -162,6 +251,16 @@ describe('Webhooks api', function () { const response = await webhookRequestSender.deleteWebhook(badWebhookValue); + expect(response.statusCode).to.equal(400); + }); + }); + describe('PUT /v1/webhooks/:webhook_id', function() { + it('should return 400 for bad uuid', async function() { + const webhook = generateWebhook(); + const badWebhookValue = 'lalallalalal'; + + const response = await webhookRequestSender.updateWebhook(badWebhookValue, webhook); + expect(response.statusCode).to.equal(400); }); }); @@ -174,6 +273,16 @@ describe('Webhooks api', function () { const response = await webhookRequestSender.getWebhook(notExistingWebhookId); + expect(response.statusCode).to.equal(404); + }); + }); + describe('PUT /v1/webhooks/:webhook_id', function () { + it('should return 404 for no existing webhook', async function() { + const notExistingWebhookId = uuid.v4(); + const webhook = generateWebhook(); + + const response = await webhookRequestSender.updateWebhook(notExistingWebhookId, webhook); + expect(response.statusCode).to.equal(404); }); }); From b454835bf558a7cb6033dc6afa4728372914b196 Mon Sep 17 00:00:00 2001 From: syncush Date: Wed, 19 Aug 2020 22:28:34 +0300 Subject: [PATCH 28/38] feat(webhooks): added support for api_failure --- src/reports/models/notifier.js | 28 ++++++++- src/webhooks/models/webhookManager.js | 3 +- src/webhooks/models/webhooksFormatter.js | 74 +----------------------- 3 files changed, 28 insertions(+), 77 deletions(-) diff --git a/src/reports/models/notifier.js b/src/reports/models/notifier.js index d5b0723f7..f5df7a593 100644 --- a/src/reports/models/notifier.js +++ b/src/reports/models/notifier.js @@ -17,7 +17,8 @@ const { WEBHOOK_EVENT_TYPE_ABORTED, WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, - WEBHOOK_EVENT_TYPE_IN_PROGRESS + WEBHOOK_EVENT_TYPE_IN_PROGRESS, + WEBHOOK_EVENT_TYPE_API_FAILURE } = require('../../common/consts'); module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { @@ -51,6 +52,10 @@ module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { await handleAbort(report, job); break; } + case constants.SUBSCRIBER_INTERMEDIATE_STAGE: { + await handleIntermediate(report, job); + break; + } default: { logger.trace(metadata, 'Handling unsupported test status: ' + JSON.stringify(stats)); break; @@ -100,7 +105,7 @@ async function handleDone(report, job, reportBenchmark) { } async function handleAbort(report, job) { - await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_ABORTED, { report }); + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_ABORTED, report); } async function getBenchmarkConfig() { @@ -109,6 +114,25 @@ async function getBenchmarkConfig() { return { benchmarkThreshold, benchmarkWebhook }; } +async function handleIntermediate(report, job) { + const reportSubscribers = report.subscribers; + const accumulatedStatusCodesCounter = reportSubscribers.reduce((accumulated, { last_stats: { codes: statusCodesCounter } }) => { + const statusCodes = Object.keys(statusCodesCounter); + for (const statusCode of statusCodes) { + if (!accumulated[statusCode]) { + accumulated[statusCode] = 0; + } + accumulated[statusCode] += Number(statusCodesCounter[statusCode]); + } + return accumulated; + }, {}); + // if there are no stats that have a status code of >= 500, do nothing + if (Object.keys(accumulatedStatusCodesCounter).every(statusCode => statusCode < 500)) { + return; + } + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_API_FAILURE, report, { accumulatedStatusCodesCounter }, { icon: ':skull:' }); +} + async function getEmailTargets(job) { let targets = []; let defaultEmailAddress = await configHandler.getConfigValue(configConstants.DEFAULT_EMAIL_ADDRESS); diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index bdc2b2f80..dc4bd293b 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -50,9 +50,8 @@ async function getAllGlobalWebhooks() { } async function fireSingleWebhook(webhook, payload) { - let webhookResponse = null; try { - webhookResponse = await requestSender.send({ + await requestSender.send({ method: 'POST', url: webhook.url, body: payload diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index ee90d41aa..95e4fcf24 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -51,78 +51,6 @@ function json(event, testId, jobId, report, additionalInfo, options) { } }; - // let additionalDetails = {}; - // switch (event) { - // case WEBHOOK_EVENT_TYPE_STARTED: { - // additionalDetails = { - // test_name: , - // environment: , - // duration: , - // arrival_rate: , - // parallelism: , - // ramp_to: rampTo, - // }; - // break; - // } - // case WEBHOOK_EVENT_TYPE_FINISHED: { - // additionalDetails = { - // test_name: , - // grafana_report: , - // aggregated_report: { - // ...aggregatedReport.aggregate - // }, - // report_benchmark: { - // ...reportBenchmark - // } - // }; - // break; - // } - // case WEBHOOK_EVENT_TYPE_FAILED: { - // additionalDetails = { - // environment: , - // stats: { - // ...stats.data - // } - // }; - // break; - // } - // case WEBHOOK_EVENT_TYPE_ABORTED: { - // additionalDetails = { - // testName, - // grafanaReport: - // }; - // break; - // } - // case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: { - // additionalDetails = { - // aggregated_test_name: , - // benchmark_threshold: , - // score: , - // last_three_scores: , - // aggregated_report: - // }; - // break; - // } - // case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { - // break; - // } - // case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { - // additionalDetails = { - - // }; - // break; - // } - // case WEBHOOK_EVENT_TYPE_API_FAILURE: { - // additionalDetails = { - - // }; - // break; - // } - // default: { - // throw unknownWebhookEventTypeError(); - // } - // } - // payload.additional_details = additionalDetails; return payload; } @@ -170,7 +98,7 @@ function slack(event, testId, jobId, report, additionalInfo, options) { break; } case WEBHOOK_EVENT_TYPE_API_FAILURE: { - message = `::boom:: *Test ${testName} with id: ${testId} has encountered an API failure!*`; + message = `::boom:: *Test ${testName} with id: ${testId} has encountered an API failure!* :skull:`; break; } default: { From 0b901ce5cf722ce9b2c44bb4f108dc208f9ea55f Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Thu, 20 Aug 2020 21:38:52 +0300 Subject: [PATCH 29/38] feat(jobs): forbid user from assigning global webhook to a job --- docs/openapi3.yaml | 6 ++++++ src/jobs/models/jobManager.js | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/openapi3.yaml b/docs/openapi3.yaml index 4d16564b8..127b8add0 100644 --- a/docs/openapi3.yaml +++ b/docs/openapi3.yaml @@ -475,6 +475,12 @@ paths: application/json: schema: $ref: '#/components/schemas/error_response' + '422': + description: Unprocessable entity + content: + application/json: + schema: + $ref: '#/components/schemas/error_response' '409': description: Conflict content: diff --git a/src/jobs/models/jobManager.js b/src/jobs/models/jobManager.js index 154067987..5a539c9b8 100644 --- a/src/jobs/models/jobManager.js +++ b/src/jobs/models/jobManager.js @@ -1,4 +1,7 @@ 'use strict'; + +const webhookManager = require('../../webhooks/models/webhookManager'); + const logger = require('../../common/logger'), uuid = require('uuid'), CronJob = require('cron').CronJob, @@ -6,6 +9,7 @@ const logger = require('../../common/logger'), util = require('util'), dockerHubConnector = require('./dockerHubConnector'), databaseConnector = require('./database/databaseConnector'), + webhooksManager = require('../../webhooks/models/webhookManager'), configConstants = require('../../common/consts').CONFIG; let jobConnector; @@ -47,6 +51,7 @@ module.exports.scheduleFinishedContainersCleanup = async () => { module.exports.createJob = async (job) => { let jobId = uuid.v4(); const configData = await configHandler.getConfig(); + await globalWebhookAssignmentGuard(job.webhooks); try { await databaseConnector.insertJob(jobId, job); logger.info('Job saved successfully to database'); @@ -137,16 +142,15 @@ module.exports.getJob = async (jobId) => { module.exports.updateJob = async (jobId, jobConfig) => { const configData = await configHandler.getConfig(); - let job = null; - let updatedJob = null; - [ job ] = await databaseConnector.getJob(jobId); + await globalWebhookAssignmentGuard(jobConfig.webhooks); + let [job] = await databaseConnector.getJob(jobId); if (!job.cron_expression) { let error = new Error('Can not update jobs from type run_immediately: true'); error.statusCode = 422; throw error; } try { - updatedJob = await databaseConnector.updateJob(jobId, jobConfig); + await databaseConnector.updateJob(jobId, jobConfig); job = await databaseConnector.getJob(jobId); } catch (err) { logger.error(err, 'Error occurred trying to update job'); @@ -289,3 +293,15 @@ function addCron(jobId, job, cronExpression, configData) { }, true); cronJobs[jobId] = scheduledJob; } + +async function globalWebhookAssignmentGuard(webhookIds) { + let webhooks = []; + if (webhookIds && webhookIds.length > 0) { + webhooks = await Promise.all(webhookIds.map(webhookId => webhookManager.getWebhook(webhookId))); + } + if (webhooks.some(webhook => webhook.global)) { + const error = new Error('Assigning global webhook to a job is not allowed!'); + error.statusCode = 422; + throw error; + } +} From 19099f87c161a893fcbee9533c52d104382464f3 Mon Sep 17 00:00:00 2001 From: syncush Date: Fri, 21 Aug 2020 14:21:05 +0300 Subject: [PATCH 30/38] feat(webhooks): benchmarks --- src/webhooks/models/webhooksFormatter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index 95e4fcf24..c85448cde 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -30,7 +30,7 @@ function getThresholdSlackMessage(state, { testName, benchmarkThreshold, lastSco } return `${icon} *Test ${testName} got a score of ${score.toFixed(1)}` + ` this is ${resultText} the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + - `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`;; + `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; } function slackWebhookFormat(message, options) { @@ -90,7 +90,7 @@ function slack(event, testId, jobId, report, additionalInfo, options) { } case WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED: case WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED: { - message = getThresholdSlackMessage(event, { testName, lastScores, benchmarkThreshold, score }); + message = getThresholdSlackMessage(event, { testName, lastScores, aggregatedReport, benchmarkThreshold, score }); break; } case WEBHOOK_EVENT_TYPE_IN_PROGRESS: { From d171440b108e3a56f31078945de20befb5470279 Mon Sep 17 00:00:00 2001 From: syncush Date: Fri, 21 Aug 2020 19:29:15 +0300 Subject: [PATCH 31/38] tests(jobs): checking global webhook assignment for a job is failing --- .../jobs/createJobGlobal-test.js | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/tests/integration-tests/jobs/createJobGlobal-test.js b/tests/integration-tests/jobs/createJobGlobal-test.js index e948ec738..c794709d9 100644 --- a/tests/integration-tests/jobs/createJobGlobal-test.js +++ b/tests/integration-tests/jobs/createJobGlobal-test.js @@ -1,12 +1,21 @@ const should = require('should'), uuid = require('uuid'), schedulerRequestCreator = require('./helpers/requestCreator'), + webhooksRequestSender = require('../webhooks/helpers/requestCreator'), + testsRequestSender = require('../tests/helpers/requestCreator'), + { WEBHOOK_EVENT_TYPE_STARTED, EVENT_FORMAT_TYPE_JSON } = require('../../../src/common/consts'), + basicTest = require('../../testExamples/Basic_test.json'), nock = require('nock'); +const { expect } = require('chai'); +const { createJob } = require('../../../src/jobs/models/jobManager'); + describe('Create job global tests', function () { this.timeout(20000); before(async () => { await schedulerRequestCreator.init(); + await webhooksRequestSender.init(); + await testsRequestSender.init(); }); beforeEach(async () => { @@ -169,5 +178,89 @@ describe('Create job global tests', function () { should(response.statusCode).eql(404); should(response.body.message).eql('Not found'); }); + + describe('Create a job with a global webhook', () => { + it('should return 422', async () => { + const globalWebhook = { + name: 'Some webhook name', + url: 'https://predator.dev', + events: [WEBHOOK_EVENT_TYPE_STARTED], + global: true, + format_type: EVENT_FORMAT_TYPE_JSON + }; + + const job = { + arrival_rate: 1, + duration: 120, + environment: 'test', + run_immediately: true, + emails: [], + parallelism: 1, + max_virtual_users: 500 + }; + const headers = { 'Content-Type': 'application/json' }; + + const webhookCreateResponse = await webhooksRequestSender.createWebhook(globalWebhook); + expect(webhookCreateResponse.status).to.be.equal(201); + const webhookId = webhookCreateResponse.body.id; + + const createTestResponse = await testsRequestSender.createTest(basicTest, headers); + expect(createTestResponse.status).to.be.equal(201); + const testId = createTestResponse.body.id; + + + job.webhooks = [webhookId]; + job.test_id = testId; + + const createJobResponse = await schedulerRequestCreator.createJob(job, headers); + expect(createJobResponse.status).to.be.equal(422); + expect(createJobResponse.body.message).to.be.equal('Assigning global webhook to a job is not allowed!'); + }); + }); + describe('Update a job with a global webhook', () => { + it('should return 422', async () => { + const globalWebhook = { + name: 'Some webhook name', + url: 'https://predator.dev', + events: [WEBHOOK_EVENT_TYPE_STARTED], + global: true, + format_type: EVENT_FORMAT_TYPE_JSON + }; + + const job = { + arrival_rate: 1, + duration: 120, + environment: 'test', + run_immediately: false, + enabled: true, + cron_expression: '* * * 1 1', + webhooks: [], + emails: [], + parallelism: 1, + max_virtual_users: 500 + }; + const headers = { 'Content-Type': 'application/json' }; + + const createTestResponse = await testsRequestSender.createTest(basicTest, headers); + expect(createTestResponse.status).to.be.equal(201); + const testId = createTestResponse.body.id; + + job.test_id = testId; + + const createJobResponse = await schedulerRequestCreator.createJob(job, headers); + expect(createJobResponse.status).to.be.equal(201); + const jobId = createJobResponse.body.id; + + const webhookCreateResponse = await webhooksRequestSender.createWebhook(globalWebhook); + expect(webhookCreateResponse.status).to.be.equal(201); + const webhookId = webhookCreateResponse.body.id; + + job.webhooks = [webhookId]; + + const updateJobResponse = await schedulerRequestCreator.updateJob(jobId, job, headers); + expect(updateJobResponse.status).to.be.equal(422); + expect(updateJobResponse.body.message).to.be.equal('Assigning global webhook to a job is not allowed!'); + }); + }); }); -}); \ No newline at end of file +}); From 836b4fca664c391cc096000d8289d831f89c766f Mon Sep 17 00:00:00 2001 From: syncush Date: Sat, 22 Aug 2020 22:10:14 +0300 Subject: [PATCH 32/38] feat(webhooks): resolved PR comments --- .../database/sequelize/sequelizeConnector.js | 6 ++---- src/jobs/models/jobManager.js | 12 ++++++------ src/webhooks/models/webhookManager.js | 16 ++++++---------- src/webhooks/models/webhooksFormatter.js | 3 +-- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index a490a0593..6471e8aa1 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -104,12 +104,10 @@ async function updateJob(jobId, jobInfo) { id: jobId } }; - let updatedJob = null; let oldJob = await job.findByPk(jobId); - await client.transaction(async function(transaction) { - updatedJob = await job.update(params, { ...options, transaction }); + const updatedJob = await client.transaction(async function(transaction) { await oldJob.setWebhooks(jobInfo.webhooks || [], { transaction }); - return updatedJob; + return job.update(params, { ...options, transaction }); }); return updatedJob; } diff --git a/src/jobs/models/jobManager.js b/src/jobs/models/jobManager.js index 5a539c9b8..8ef836c42 100644 --- a/src/jobs/models/jobManager.js +++ b/src/jobs/models/jobManager.js @@ -144,6 +144,11 @@ module.exports.updateJob = async (jobId, jobConfig) => { const configData = await configHandler.getConfig(); await globalWebhookAssignmentGuard(jobConfig.webhooks); let [job] = await databaseConnector.getJob(jobId); + if (job.length === 0) { + let error = new Error('Not found'); + error.statusCode = 404; + throw error; + } if (!job.cron_expression) { let error = new Error('Can not update jobs from type run_immediately: true'); error.statusCode = 422; @@ -156,11 +161,6 @@ module.exports.updateJob = async (jobId, jobConfig) => { logger.error(err, 'Error occurred trying to update job'); throw err; } - if (job.length === 0) { - let error = new Error('Not found'); - error.statusCode = 404; - throw error; - } if (cronJobs[jobId]) { cronJobs[jobId].stop(); delete cronJobs[jobId]; @@ -300,7 +300,7 @@ async function globalWebhookAssignmentGuard(webhookIds) { webhooks = await Promise.all(webhookIds.map(webhookId => webhookManager.getWebhook(webhookId))); } if (webhooks.some(webhook => webhook.global)) { - const error = new Error('Assigning global webhook to a job is not allowed!'); + const error = new Error('Assigning a global webhook to a job is not allowed'); error.statusCode = 422; throw error; } diff --git a/src/webhooks/models/webhookManager.js b/src/webhooks/models/webhookManager.js index dc4bd293b..8c2aa29ea 100644 --- a/src/webhooks/models/webhookManager.js +++ b/src/webhooks/models/webhookManager.js @@ -63,17 +63,13 @@ async function fireSingleWebhook(webhook, payload) { } } -//format, eventType, jobId, testId, report, additionalInfo = {}, options = {} -function fireWebhooks(webhooks, eventType, jobId, testId, report, additionalInfo, options) { - return webhooks.map(webhook => fireSingleWebhook(webhook, webhooksFormatter(webhook.format_type, eventType, jobId, testId, report, additionalInfo, options))); +function fireWebhooksPromisesArray(webhooks, eventType, jobId, testId, report, additionalInfo, options) { + return webhooks.map(webhook => { + const webhookPayload = webhooksFormatter(webhook.format_type, eventType, jobId, testId, report, additionalInfo, options); + return fireSingleWebhook(webhook, webhookPayload); + }); } -// FAILED: report, stats -// STARTED: report -// IN_PROGRESS: report, aggregatedReport -// report, aggregatedReport, reportBenchmark -// BENCHMARK_FAILED/PASSED: report, aggregatedReport, score, lastScores, icon -// ABORTED: report async function fireWebhookByEvent(job, eventType, report, additionalInfo = {}, options = {}) { const jobWebhooks = await Promise.all(job.webhooks.map(webhookId => getWebhook(webhookId))); const globalWebhooks = await getAllGlobalWebhooks(); @@ -82,7 +78,7 @@ async function fireWebhookByEvent(job, eventType, report, additionalInfo = {}, o if (webhooksWithEventType.length === 0) { return; } - const webhooksPromises = fireWebhooks(webhooksWithEventType, eventType, job.id, job.test_id, report, additionalInfo, options); + const webhooksPromises = fireWebhooksPromisesArray(webhooksWithEventType, eventType, job.id, job.test_id, report, additionalInfo, options); await Promise.allSettled(webhooksPromises); } diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index c85448cde..fab5829c9 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -47,8 +47,7 @@ function json(event, testId, jobId, report, additionalInfo, options) { job_id: jobId, event: event, additional_details: { - ...cloneDeep({ report, ...additionalInfo }), - + ...cloneDeep({ report, ...additionalInfo }) } }; return payload; From d93b2784a1402162f65bc81458016f542b209c14 Mon Sep 17 00:00:00 2001 From: syncush Date: Tue, 25 Aug 2020 22:21:29 +0300 Subject: [PATCH 33/38] test(webhooks): adding unit tests --- .../database/sequelize/sequelizeConnector.js | 7 +- src/jobs/models/jobManager.js | 13 +- src/webhooks/models/webhooksFormatter.js | 9 +- .../unit-tests/jobs/models/jobManager-test.js | 654 +++++++++++++----- .../jobs/sequelize/sequelizeConnector-test.js | 4 +- .../sequelize/webhooksFormatter-test.js | 233 +++++++ 6 files changed, 714 insertions(+), 206 deletions(-) create mode 100644 tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js diff --git a/src/jobs/models/database/sequelize/sequelizeConnector.js b/src/jobs/models/database/sequelize/sequelizeConnector.js index 6471e8aa1..6f6d875f9 100644 --- a/src/jobs/models/database/sequelize/sequelizeConnector.js +++ b/src/jobs/models/database/sequelize/sequelizeConnector.js @@ -43,12 +43,11 @@ async function insertJob(jobId, jobInfo) { if (params.emails) { include.push({ association: job.email }); } - let createdJob = null; await client.transaction(async function(transaction) { - createdJob = await job.create(params, { include, transaction }); - return createdJob.setWebhooks(jobInfo.webhooks || [], { transaction }); + const createdJobTransaction = await job.create(params, { include, transaction }); + createdJobTransaction.setWebhooks(jobInfo.webhooks || [], { transaction }); + return createdJobTransaction; }); - return createdJob; } async function getJobsAndParse(jobId) { diff --git a/src/jobs/models/jobManager.js b/src/jobs/models/jobManager.js index 8ef836c42..685a6859f 100644 --- a/src/jobs/models/jobManager.js +++ b/src/jobs/models/jobManager.js @@ -1,7 +1,5 @@ 'use strict'; -const webhookManager = require('../../webhooks/models/webhookManager'); - const logger = require('../../common/logger'), uuid = require('uuid'), CronJob = require('cron').CronJob, @@ -57,7 +55,7 @@ module.exports.createJob = async (job) => { logger.info('Job saved successfully to database'); let latestDockerImage = await dockerHubConnector.getMostRecentRunnerTag(); let runId = Date.now(); - let jobSpecificPlatformRequest = createJobRequest(jobId, runId, job, latestDockerImage, configData); + let jobSpecificPlatformRequest = await createJobRequest(jobId, runId, job, latestDockerImage, configData); if (job.run_immediately) { await jobConnector.runJob(jobSpecificPlatformRequest); } @@ -199,7 +197,7 @@ function createResponse(jobId, jobBody, runId) { return response; } -function createJobRequest(jobId, runId, jobBody, dockerImage, configData) { +async function createJobRequest(jobId, runId, jobBody, dockerImage, configData) { const jobTemplate = require(`./${configData.job_platform.toLowerCase()}/jobTemplate`); let jobName = util.format(JOB_PLATFORM_NAME, jobId); let rampToPerRunner = jobBody.ramp_to; @@ -248,7 +246,8 @@ function createJobRequest(jobId, runId, jobBody, dockerImage, configData) { environmentVariables.EMAILS = jobBody.emails.join(';'); } if (jobBody.webhooks) { - environmentVariables.WEBHOOKS = jobBody.webhooks.join(';'); + const webhooks = await Promise.all(jobBody.webhooks.map(id => webhooksManager.getWebhook(id))); + environmentVariables.WEBHOOKS = webhooks.map(({ url }) => url).join(';'); } if (rampToPerRunner) { environmentVariables.RAMP_TO = rampToPerRunner.toString(); @@ -282,7 +281,7 @@ function addCron(jobId, job, cronExpression, configData) { } else { let latestDockerImage = await dockerHubConnector.getMostRecentRunnerTag(); let runId = Date.now(); - let jobSpecificPlatformConfig = createJobRequest(jobId, runId, job, latestDockerImage, configData); + let jobSpecificPlatformConfig = await createJobRequest(jobId, runId, job, latestDockerImage, configData); await jobConnector.runJob(jobSpecificPlatformConfig); } } catch (error) { @@ -297,7 +296,7 @@ function addCron(jobId, job, cronExpression, configData) { async function globalWebhookAssignmentGuard(webhookIds) { let webhooks = []; if (webhookIds && webhookIds.length > 0) { - webhooks = await Promise.all(webhookIds.map(webhookId => webhookManager.getWebhook(webhookId))); + webhooks = await Promise.all(webhookIds.map(webhookId => webhooksManager.getWebhook(webhookId))); } if (webhooks.some(webhook => webhook.global)) { const error = new Error('Assigning a global webhook to a job is not allowed'); diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index fab5829c9..e64c5b923 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -15,7 +15,7 @@ const { WEBHOOK_SLACK_DEFAULT_REPORTER_NAME, WEBHOOK_EVENT_TYPE_IN_PROGRESS } = require('../../common/consts'); -const statsFromatter = require('./statsFormatter'); +const statsFormatter = require('./statsFormatter'); function unknownWebhookEventTypeError(badWebhookEventTypeValue) { return new Error(`Unrecognized webhook event: ${badWebhookEventTypeValue}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`); @@ -30,7 +30,7 @@ function getThresholdSlackMessage(state, { testName, benchmarkThreshold, lastSco } return `${icon} *Test ${testName} got a score of ${score.toFixed(1)}` + ` this is ${resultText} the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + - `.*\n${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; + `.*\n${statsFormatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, { score })}\n`; } function slackWebhookFormat(message, options) { @@ -73,7 +73,7 @@ function slack(event, testId, jobId, report, additionalInfo, options) { break; } case WEBHOOK_EVENT_TYPE_FINISHED: { - message = `😎 *Test ${testName} with id: ${testId} is finished.*\n ${statsFromatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; + message = `😎 *Test ${testName} with id: ${testId} is finished.*\n ${statsFormatter.getStatsFormatted('aggregate', aggregatedReport.aggregate, reportBenchmark)}\n`; break; } case WEBHOOK_EVENT_TYPE_FAILED: { @@ -111,6 +111,9 @@ function slack(event, testId, jobId, report, additionalInfo, options) { } module.exports = function(format, eventType, jobId, testId, report, additionalInfo = {}, options = {}) { + if (!WEBHOOK_EVENT_TYPES.includes(eventType)) { + throw unknownWebhookEventTypeError(eventType); + } switch (format) { case EVENT_FORMAT_TYPE_SLACK: { return slack(eventType, testId, jobId, report, additionalInfo, options); diff --git a/tests/unit-tests/jobs/models/jobManager-test.js b/tests/unit-tests/jobs/models/jobManager-test.js index 73bd46a79..bfca73bea 100644 --- a/tests/unit-tests/jobs/models/jobManager-test.js +++ b/tests/unit-tests/jobs/models/jobManager-test.js @@ -1,4 +1,5 @@ 'use strict'; + const should = require('should'), rewire = require('rewire'), sinon = require('sinon'), @@ -8,6 +9,7 @@ const should = require('should'), jobConnector = require('../../../../src/jobs/models/kubernetes/jobConnector'), dockerHubConnector = require('../../../../src/jobs/models/dockerHubConnector'), jobTemplate = require('../../../../src/jobs/models/kubernetes/jobTemplate'), + webhooksManager = require('../../../../src/webhooks/models/webhookManager'), config = require('../../../../src/common/consts').CONFIG; let manager; @@ -15,104 +17,9 @@ let manager; const TEST_ID = '5a9eee73-cf56-47aa-ac77-fad59e961aaa'; const JOB_ID = '5a9eee73-cf56-47aa-ac77-fad59e961aaf'; -const jobBodyWithCron = { - test_id: TEST_ID, - id: TEST_ID, - arrival_rate: 1, - duration: 1, - cron_expression: '* * * * * *', - run_immediately: true, - emails: ['dina@niv.eli'], - environment: 'test', - ramp_to: '1', - webhooks: ['dina', 'niv', 'eli'] -}; -const jobBodyWithCronNotImmediately = { - test_id: TEST_ID, - arrival_rate: 1, - duration: 1, - cron_expression: '', - run_immediately: false, - emails: ['dina@niv.eli'], - environment: 'test', - ramp_to: '1', - webhooks: ['dina', 'niv', 'eli'] -}; -const jobBodyWithoutCron = { - test_id: TEST_ID, - arrival_rate: 1, - duration: 1, - run_immediately: true, - emails: ['dina@niv.eli'], - environment: 'test', - ramp_to: '1', - webhooks: ['dina', 'niv', 'eli'] -}; - -const jobBodyWithParallelismThatSplitsNicely = { - test_id: TEST_ID, - arrival_rate: 99, - duration: 1, - run_immediately: true, - emails: ['dina@niv.eli'], - environment: 'test', - ramp_to: '150', - parallelism: 3, - webhooks: ['dina', 'niv', 'eli'], - max_virtual_users: 198 -}; - -const jobBodyWithParallelismThatSplitsWithDecimal = { - test_id: TEST_ID, - arrival_rate: 99, - duration: 1, - run_immediately: true, - emails: ['dina@niv.eli'], - environment: 'test', - ramp_to: '150', - parallelism: 20, - webhooks: ['dina', 'niv', 'eli'], - max_virtual_users: 510 -}; - -const jobBodyWithoutRampTo = { - test_id: TEST_ID, - arrival_rate: 1, - duration: 1, - run_immediately: true, - emails: ['dina@niv.eli'], - environment: 'test', - webhooks: ['dina', 'niv', 'eli'] -}; - -const jobBodyWithEnabledFalse = { - test_id: TEST_ID, - arrival_rate: 1, - duration: 1, - run_immediately: true, - emails: ['dina@niv.eli'], - environment: 'test', - webhooks: ['dina', 'niv', 'eli'], - enabled: false -}; - -const jobBodyWithCustomEnvVars = { - test_id: TEST_ID, - arrival_rate: 1, - duration: 1, - run_immediately: true, - emails: ['dina@niv.eli'], - environment: 'test', - webhooks: ['dina', 'niv', 'eli'], - custom_env_vars: { 'KEY1': 'A', 'KEY2': 'B' }, - max_virtual_users: 100 -}; - -// TODO: this tests suite runs over cassandra, cassandra should be dropped - describe('Manager tests', function () { let sandbox; - let cassandraInsertStub; + let databaseConnectorInsertStub; let loggerErrorStub; let loggerInfoStub; let jobConnectorRunJobStub; @@ -120,32 +27,42 @@ describe('Manager tests', function () { let jobGetLogsStub; let jobDeleteContainerStub; let uuidStub; - let cassandraDeleteStub; - let cassandraGetStub; - let cassandraGetSingleJobStub; - let cassandraUpdateJobStub; + let databaseConnectorDeleteStub; + let databaseConnectorGetStub; + let databaseConnectorGetSingleJobStub; + let databaseConnectorUpdateJobStub; let dockerHubConnectorGetMostRecentTagStub; let jobTemplateCreateJobRequestStub; let getConfigValueStub; + let webhooksManagerGetWebhook; + before(() => { sandbox = sinon.sandbox.create(); - cassandraInsertStub = sandbox.stub(databaseConnector, 'insertJob'); - cassandraGetStub = sandbox.stub(databaseConnector, 'getJobs'); - cassandraGetSingleJobStub = sandbox.stub(databaseConnector, 'getJob'); - cassandraDeleteStub = sandbox.stub(databaseConnector, 'deleteJob'); - cassandraUpdateJobStub = sandbox.stub(databaseConnector, 'updateJob'); + databaseConnectorInsertStub = sandbox.stub(databaseConnector, 'insertJob'); + databaseConnectorGetStub = sandbox.stub(databaseConnector, 'getJobs'); + databaseConnectorGetSingleJobStub = sandbox.stub(databaseConnector, 'getJob'); + databaseConnectorDeleteStub = sandbox.stub(databaseConnector, 'deleteJob'); + databaseConnectorUpdateJobStub = sandbox.stub(databaseConnector, 'updateJob'); + + webhooksManagerGetWebhook = sandbox.stub(webhooksManager, 'getWebhook'); + jobGetLogsStub = sandbox.stub(jobConnector, 'getLogs'); jobDeleteContainerStub = sandbox.stub(jobConnector, 'deleteAllContainers'); jobStopRunStub = sandbox.stub(jobConnector, 'stopRun'); + loggerErrorStub = sandbox.stub(logger, 'error'); loggerInfoStub = sandbox.stub(logger, 'info'); + jobConnectorRunJobStub = sandbox.stub(jobConnector, 'runJob'); + dockerHubConnectorGetMostRecentTagStub = sandbox.stub(dockerHubConnector, 'getMostRecentRunnerTag'); uuidStub = sandbox.stub(uuid, 'v4'); + jobTemplateCreateJobRequestStub = sandbox.spy(jobTemplate, 'createJobRequest'); getConfigValueStub = sandbox.stub(); + manager = rewire('../../../../src/jobs/models/jobManager'); manager.__set__('configHandler', { @@ -175,21 +92,33 @@ describe('Manager tests', function () { dockerHubConnectorGetMostRecentTagStub.resolves(); }); - it('Cassandra connector returns an empty array', async () => { - cassandraGetStub.resolves([]); + it('databaseConnector connector returns an empty array', async () => { + databaseConnectorGetStub.resolves([]); await manager.reloadCronJobs(); manager.__get__('cronJobs').should.eql({}); }); - it('Cassandra connector returns an array with job with no schedules', async () => { - cassandraGetStub.resolves([{ cron_expression: null }]); + it('databaseConnector connector returns an array with job with no schedules', async () => { + databaseConnectorGetStub.resolves([{ cron_expression: null }]); await manager.reloadCronJobs(); manager.__get__('cronJobs').should.eql({}); }); - describe('Cassandra connector returns an array with job with schedules, and job exist and can be run', function () { + describe('databaseConnector connector returns an array with job with schedules, and job exist and can be run', function () { it('Verify job added', async function () { - cassandraGetStub.resolves([jobBodyWithCron]); + const jobBodyWithCron = { + test_id: TEST_ID, + id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * * * *', + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: [] + }; + databaseConnectorGetStub.resolves([jobBodyWithCron]); await manager.reloadCronJobs(); manager.__get__('cronJobs').should.have.key('5a9eee73-cf56-47aa-ac77-fad59e961aaa'); }); @@ -256,29 +185,53 @@ describe('Manager tests', function () { uuidStub.returns('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); }); - it('Simple request with custom env vars, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request with custom env vars, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithCustomEnvVars = { + test_id: TEST_ID, + arrival_rate: 1, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + webhooks: webhooks.map(({ id }) => id), + custom_env_vars: { 'KEY1': 'A', 'KEY2': 'B' }, + max_virtual_users: 100 + }; jobConnectorRunJobStub.resolves({ id: 'run_id' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 1, duration: 1, max_virtual_users: 100, enabled: true, - 'custom_env_vars': - { - 'KEY1': 'A', - 'KEY2': 'B' - } + 'custom_env_vars': { + KEY1: 'A', + KEY2: 'B' + } }; - + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); let jobResponse = await manager.createJob(jobBodyWithCustomEnvVars); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); jobTemplateCreateJobRequestStub.args[0][3].should.containEql({ JOB_ID: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', @@ -288,7 +241,7 @@ describe('Manager tests', function () { ARRIVAL_RATE: '1', DURATION: '1', EMAILS: 'dina@niv.eli', - WEBHOOKS: 'dina;niv;eli', + WEBHOOKS: webhooks.map(({ url }) => url).join(';'), CUSTOM_KEY1: 'A', CUSTOM_KEY2: 'B' }); @@ -296,29 +249,80 @@ describe('Manager tests', function () { jobTemplateCreateJobRequestStub.args[0][3].should.have.key('RUN_ID'); }); - it('Simple request, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithoutCron = { + test_id: TEST_ID, + arrival_rate: 1, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: webhooks.map(({ id }) => id) + }; jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', ramp_to: '1', test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 1, duration: 1 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); let jobResponse = await manager.createJob(jobBodyWithoutCron); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); }); - it('Simple request, with parallelism, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request, with parallelism, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithParallelismThatSplitsNicely = { + test_id: TEST_ID, + arrival_rate: 99, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '150', + parallelism: 3, + webhooks: webhooks.map(({ id }) => id), + max_virtual_users: 198 + }; jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', ramp_to: '150', @@ -326,15 +330,16 @@ describe('Manager tests', function () { environment: 'test', emails: ['dina@niv.eli'], parallelism: 3, - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 99, duration: 1, max_virtual_users: 198 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); let jobResponse = await manager.createJob(jobBodyWithParallelismThatSplitsNicely); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); should(jobConnectorRunJobStub.args[0][0].spec.parallelism).eql(3); @@ -354,8 +359,34 @@ describe('Manager tests', function () { }); it('Simple request, with parallelism, and arrival rate splits with decimal point, should round up', async () => { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithParallelismThatSplitsWithDecimal = { + test_id: TEST_ID, + arrival_rate: 99, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '150', + parallelism: 20, + webhooks: webhooks.map(({ id }) => id), + max_virtual_users: 510 + }; jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', ramp_to: '150', @@ -363,15 +394,16 @@ describe('Manager tests', function () { environment: 'test', emails: ['dina@niv.eli'], parallelism: 20, - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 99, duration: 1, max_virtual_users: 510 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); let jobResponse = await manager.createJob(jobBodyWithParallelismThatSplitsWithDecimal); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); should(jobConnectorRunJobStub.args[0][0].spec.parallelism).eql(20); @@ -390,34 +422,68 @@ describe('Manager tests', function () { should(maxVirtualUsers.value).eql('26'); }); - it('Simple request without ramp to, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request without ramp to, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithoutRampTo = { + test_id: TEST_ID, + arrival_rate: 1, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + webhooks: webhooks.map(({ id }) => id) + }; jobConnectorRunJobStub.resolves({ id: 'run_id' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 1, duration: 1 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); let jobResponse = await manager.createJob(jobBodyWithoutRampTo); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); }); it('Simple request with enabled as false', async () => { + const jobBodyWithEnabledFalse = { + test_id: TEST_ID, + arrival_rate: 1, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + webhooks: ['5a9eee73-cf56-47aa-ac77-fad59e961aaa', '5a9eee73-cf56-47aa-ac77-fad59e961aab'], + enabled: false + }; jobConnectorRunJobStub.resolves({ id: 'run_id' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: ['5a9eee73-cf56-47aa-ac77-fad59e961aaa', '5a9eee73-cf56-47aa-ac77-fad59e961aab'], arrival_rate: 1, duration: 1, enabled: false @@ -425,25 +491,71 @@ describe('Manager tests', function () { let jobResponse = await manager.createJob(jobBodyWithEnabledFalse); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); }); - it('Fail to save job to cassandra', function () { - cassandraInsertStub.rejects({ error: 'cassandra error' }); + it('Fail to save job to databaseConnector', function () { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithoutRampTo = { + test_id: TEST_ID, + arrival_rate: 1, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + webhooks: webhooks.map(({ id }) => id) + }; + databaseConnectorInsertStub.rejects({ error: 'databaseConnector error' }); return manager.createJob(jobBodyWithoutRampTo) .catch(function (error) { jobConnectorRunJobStub.callCount.should.eql(0); loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0].should.eql([{ error: 'cassandra error' }, 'Error occurred trying to create new job']); - error.should.eql({ error: 'cassandra error' }); + loggerErrorStub.args[0].should.eql([{ error: 'databaseConnector error' }, 'Error occurred trying to create new job']); + error.should.eql({ error: 'databaseConnector error' }); }); }); it('Fail to create a job', function () { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithoutRampTo = { + test_id: TEST_ID, + arrival_rate: 1, + duration: 1, + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + webhooks: webhooks.map(({ id }) => id) + }; jobConnectorRunJobStub.rejects({ error: 'job creator error' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); return manager.createJob(jobBodyWithoutRampTo) .catch(function (error) { @@ -454,14 +566,40 @@ describe('Manager tests', function () { }); }); - describe('Request with cron expression, should save new job to cassandra, deploy the job and return the job id and the job configuration', function () { + describe('Request with cron expression, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', function () { before(() => { jobConnectorRunJobStub.resolves({}); }); it('Validate response', function () { - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithCron = { + test_id: TEST_ID, + id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * * * *', + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: webhooks.map(({ id }) => id) + }; + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let expectedResult = { 'cron_expression': '* * * * * *', ramp_to: '1', @@ -469,10 +607,11 @@ describe('Manager tests', function () { test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 1, duration: 1 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); return manager.createJob(jobBodyWithCron) .then(function (result) { @@ -495,14 +634,40 @@ describe('Manager tests', function () { }); }); - describe('Request with cron expression and enabled=false should save new job to cassandra, deploy the job and return the job id and the job configuration and not run the job', function () { + describe('Request with cron expression and enabled=false should save new job to databaseConnector, deploy the job and return the job id and the job configuration and not run the job', function () { before(() => { jobConnectorRunJobStub.resolves({}); }); it('Validate response', function () { - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithCron = { + test_id: TEST_ID, + id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * * * *', + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: webhooks.map(({ id }) => id) + }; + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let expectedResult = { 'cron_expression': '* * * * * *', ramp_to: '1', @@ -510,12 +675,13 @@ describe('Manager tests', function () { test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 1, duration: 1 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); - let jobBodyWithCronDisabled = {...jobBodyWithCron, enabled: false}; + let jobBodyWithCronDisabled = { ...jobBodyWithCron, enabled: false }; return manager.createJob(jobBodyWithCronDisabled) .then(function (result) { result.should.containEql(expectedResult); @@ -544,9 +710,35 @@ describe('Manager tests', function () { }); it('Validate response', function () { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithCron = { + test_id: TEST_ID, + id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * * * *', + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: webhooks.map(({ id }) => id) + }; jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let expectedResult = { cron_expression: '* * * * * *', ramp_to: '1', @@ -554,10 +746,11 @@ describe('Manager tests', function () { test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 1, duration: 1 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); return manager.createJob(jobBodyWithCron) .then(function (result) { @@ -581,9 +774,34 @@ describe('Manager tests', function () { describe('Request with cron expression, that is not invoked immediately', function () { it('Validate response', function () { + const webhooks = [ + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: '5a9eee73-cf56-47aa-ac77-fad59e961aab', + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithCronNotImmediately = { + test_id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '', + run_immediately: false, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: webhooks.map(({ id }) => id) + }; jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let date = new Date(); date.setSeconds(date.getSeconds() + 5); jobBodyWithCronNotImmediately.cron_expression = date.getSeconds() + ' * * * * *'; @@ -594,10 +812,11 @@ describe('Manager tests', function () { test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', environment: 'test', emails: ['dina@niv.eli'], - webhooks: ['dina', 'niv', 'eli'], + webhooks: webhooks.map(({ id }) => id), arrival_rate: 1, duration: 1 }; + webhooks.map(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); return manager.createJob(jobBodyWithCronNotImmediately) .then(function (result) { @@ -649,10 +868,36 @@ describe('Manager tests', function () { }); it('Should update job successfully', async function () { + const webhooks = [ + { + id: uuid.v4(), + name: 'dina', + url: 'dina@mail.com', + global: false + }, + { + id: uuid.v4(), + name: 'niv', + url: 'niv@mail.com', + global: false + } + ]; + const jobBodyWithCron = { + test_id: TEST_ID, + id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * * * *', + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: webhooks.map(({ id }) => id) + }; jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraUpdateJobStub.resolves({}); - cassandraGetSingleJobStub.resolves([{ + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorUpdateJobStub.resolves({}); + databaseConnectorGetSingleJobStub.resolves([{ id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: 'secondId', environment: 'test', @@ -660,9 +905,10 @@ describe('Manager tests', function () { duration: 1, cron_expression: '20 * * * *', emails: null, - webhooks: ['dina', 'niv'], + webhooks: jobBodyWithCron.webhooks, ramp_to: '1' }]); + webhooks.forEach(webhook => webhooksManagerGetWebhook.withArgs(webhook.id).resolves(webhook)); await manager.createJob(jobBodyWithCron); await manager.updateJob('5a9eee73-cf56-47aa-ac77-fad59e961aaf', { cron_expression: '20 * * * *' }); loggerInfoStub.callCount.should.eql(4); @@ -670,11 +916,23 @@ describe('Manager tests', function () { await manager.deleteJob('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); }); - it('Updating data in cassandra fails', async function () { + it('Updating data in databaseConnector fails', async function () { + const jobBodyWithCron = { + test_id: TEST_ID, + id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * * * *', + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: [] + }; try { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraUpdateJobStub.rejects({ error: 'error' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorUpdateJobStub.rejects({ error: 'error' }); await manager.createJob(jobBodyWithCron); await manager.updateJob('5a9eee73-cf56-47aa-ac77-fad59e961aaf', { cron_expression: '20 * * * *' }); } catch (error) { @@ -689,6 +947,18 @@ describe('Manager tests', function () { describe('Delete scheduled job', function () { it('Deletes an existing job', async function () { + const jobBodyWithCron = { + test_id: TEST_ID, + id: TEST_ID, + arrival_rate: 1, + duration: 1, + cron_expression: '* * * * * *', + run_immediately: true, + emails: ['dina@niv.eli'], + environment: 'test', + ramp_to: '1', + webhooks: [] + }; manager.__set__('configHandler', { getConfig: () => { return { @@ -703,12 +973,12 @@ describe('Manager tests', function () { }); uuidStub.returns('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); await manager.createJob(jobBodyWithCron); await manager.deleteJob('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); - cassandraDeleteStub.callCount.should.eql(1); + databaseConnectorDeleteStub.callCount.should.eql(1); loggerInfoStub.args[2].should.eql(['Job: 5a9eee73-cf56-47aa-ac77-fad59e961aaf completed.']); }); }); @@ -726,7 +996,7 @@ describe('Manager tests', function () { describe('Get jobs', function () { it('Get a list of all jobs - also one time jobs', async function () { - cassandraGetStub.resolves([{ + databaseConnectorGetStub.resolves([{ id: 'id', test_id: 'test_id', environment: 'test', @@ -793,11 +1063,11 @@ describe('Manager tests', function () { }]; let jobs = await manager.getJobs(true); jobs.should.eql(expectedResult); - cassandraGetStub.callCount.should.eql(1); + databaseConnectorGetStub.callCount.should.eql(1); }); it('Get a list of jobs - only scheduled jobs', async function () { - cassandraGetStub.resolves([{ + databaseConnectorGetStub.resolves([{ id: 'id', test_id: 'test_id', environment: 'test', @@ -842,34 +1112,34 @@ describe('Manager tests', function () { }]; let jobs = await manager.getJobs(); jobs.should.eql(expectedResult); - cassandraGetStub.callCount.should.eql(1); + databaseConnectorGetStub.callCount.should.eql(1); }); it('Get empty list of jobs', async function () { - cassandraGetStub.resolves([]); + databaseConnectorGetStub.resolves([]); let jobs = await manager.getJobs(); jobs.should.eql([]); - cassandraGetStub.callCount.should.eql(1); + databaseConnectorGetStub.callCount.should.eql(1); loggerInfoStub.callCount.should.eql(1); }); - it('Fail to get jobs from cassandra', function () { - cassandraGetStub.rejects({ error: 'cassandra error' }); + it('Fail to get jobs from databaseConnector', function () { + databaseConnectorGetStub.rejects({ error: 'databaseConnector error' }); return manager.getJobs() .catch(function (error) { jobConnectorRunJobStub.callCount.should.eql(0); loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0].should.eql([{ error: 'cassandra error' }, 'Error occurred trying to get jobs']); - error.should.eql({ error: 'cassandra error' }); + loggerErrorStub.args[0].should.eql([{ error: 'databaseConnector error' }, 'Error occurred trying to get jobs']); + error.should.eql({ error: 'databaseConnector error' }); }); }); }); describe('Get job', function () { it('Get a list of jobs', async function () { - cassandraGetSingleJobStub.resolves([{ + databaseConnectorGetSingleJobStub.resolves([{ id: 'id', test_id: 'test_id', environment: 'test', @@ -905,11 +1175,11 @@ describe('Manager tests', function () { let job = await manager.getJob('id'); job.should.eql(expectedResult); - cassandraGetSingleJobStub.callCount.should.eql(1); + databaseConnectorGetSingleJobStub.callCount.should.eql(1); }); - it('cassandra returns empty list, should return job not found', function () { - cassandraGetSingleJobStub.resolves([]); + it('databaseConnector returns empty list, should return job not found', function () { + databaseConnectorGetSingleJobStub.resolves([]); return manager.getJob('id') .then(function () { return Promise.reject(new Error('Should not get here')); @@ -920,8 +1190,8 @@ describe('Manager tests', function () { }); }); - it('cassandra returns list of few rows, should return error', function () { - cassandraGetSingleJobStub.resolves(['one row', 'second row']); + it('databaseConnector returns list of few rows, should return error', function () { + databaseConnectorGetSingleJobStub.resolves(['one row', 'second row']); return manager.getJob('id') .then(function () { return Promise.reject(new Error('Should not get here')); @@ -932,15 +1202,15 @@ describe('Manager tests', function () { }); }); - it('Fail to get jobs from cassandra', function () { - cassandraGetSingleJobStub.rejects({ error: 'cassandra error' }); + it('Fail to get jobs from databaseConnector', function () { + databaseConnectorGetSingleJobStub.rejects({ error: 'databaseConnector error' }); return manager.getJob('id') .catch(function (error) { jobConnectorRunJobStub.callCount.should.eql(0); loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0].should.eql([{ error: 'cassandra error' }, 'Error occurred trying to get job']); - error.should.eql({ error: 'cassandra error' }); + loggerErrorStub.args[0].should.eql([{ error: 'databaseConnector error' }, 'Error occurred trying to get job']); + error.should.eql({ error: 'databaseConnector error' }); }); }); }); @@ -987,4 +1257,8 @@ describe('Manager tests', function () { } }); }); + + describe('meow', function() { + + }); }); diff --git a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js index c737f6c08..2d60dac98 100644 --- a/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/jobs/sequelize/sequelizeConnector-test.js @@ -134,10 +134,10 @@ describe('Sequelize client tests', function () { createdJob.setWebhooks.resolves(); sequelizeCreateStub.resolves(createdJob); - sequelizeTransactionStub.resolves(); + sequelizeTransactionStub.resolves(createdJob); await sequelizeConnector.insertJob(id, job); - await sequelizeTransactionStub.yields(transaction); + await sequelizeTransactionStub.yield(transaction); const jobParams = cloneDeep(job); jobParams.emails = createdJob.dataValues.emails; diff --git a/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js b/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js new file mode 100644 index 000000000..7b7c71a8f --- /dev/null +++ b/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js @@ -0,0 +1,233 @@ +const sinon = require('sinon'); +const { expect } = require('chai'); +const uuid = require('uuid'); +const rewire = require('rewire'); + +const { + EVENT_FORMAT_TYPE_JSON, + EVENT_FORMAT_TYPE_SLACK, + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_API_FAILURE, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS, + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPES +} = require('../../../../src/common/consts'); +const webhooksFormatter = rewire('../../../../src/webhooks/models/webhooksFormatter'); + +describe('webhooksFormatter', function () { + let sandbox; + let statsFormatterStub; + before('Setup', function() { + sandbox = sinon.sandbox.create(); + statsFormatterStub = sandbox.stub(); + webhooksFormatter.__set__('statsFormatter.getStatsFormatted', statsFormatterStub); + // statsFormatterStub = sandbox.stub(webhooksFormatter, 'statsFormatter.getStatsFormatted'); + }); + afterEach(() => { + sandbox.resetHistory(); + }); + + after(() => { + sandbox.restore(); + }); + describe(EVENT_FORMAT_TYPE_SLACK, function() { + it(WEBHOOK_EVENT_TYPE_STARTED, function() { + const testId = uuid.v4(); + const jobId = uuid.v4(); + const report = { + ramp_to: 100, + test_name: 'some test name', + arrival_rate: 5, + duration: 120, + environment: 'test', + parallelism: 10 + }; + const expectedResult = `🤓 *Test ${report.test_name} with id: ${testId} has started*.\n + *test configuration:* environment: ${report.environment} duration: ${report.duration} seconds, arrival rate: ${report.arrival_rate} scenarios per second, number of runners: ${report.parallelism}, ramp to: ${report.ramp_to} scenarios per second`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_STARTED, jobId, testId, report); + + expect(payload.text).to.be.equal(expectedResult); + }); + it(WEBHOOK_EVENT_TYPE_ABORTED, function () { + const testId = uuid.v4(); + const report = { + test_name: 'some name' + }; + const expectedResult = `😢 *Test ${report.test_name} with id: ${testId} was aborted.*\n`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_ABORTED, uuid.v4(), testId, report); + + expect(payload.text).to.be.equal(expectedResult); + }); + it(WEBHOOK_EVENT_TYPE_FINISHED, function () { + const testId = uuid.v4(); + const report = { + test_name: 'some name' + }; + const additionalInfo = { + aggregatedReport: { + aggregate: { + key: 'value' + } + } + }; + const stats = 'some stats string'; + statsFormatterStub.returns(stats); + + const expectedResult = `😎 *Test ${report.test_name} with id: ${testId} is finished.*\n ${stats}\n`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_FINISHED, uuid.v4(), testId, report, additionalInfo); + + expect(payload.text).to.be.equal(expectedResult); + }); + it(WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, function () { + const testId = uuid.v4(); + const report = { + test_name: 'some name' + }; + const additionalInfo = { + lastScores: [25, 60, 12], + score: 45, + benchmarkThreshold: 90, + aggregatedReport: { + aggregate: { + key: 'value' + } + } + }; + const statsText = 'some text'; + statsFormatterStub.returns(statsText); + const expectedResult = `:sad_1: *Test ${report.test_name} got a score of ${additionalInfo.score.toFixed(1)}` + + ` this is below the threshold of ${additionalInfo.benchmarkThreshold}. last 3 scores are: ${additionalInfo.lastScores.join()}` + + `.*\n${statsText}\n`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, uuid.v4(), testId, report, additionalInfo); + + expect(payload.text).to.be.equal(expectedResult); + }); + it(WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, function () { + const testId = uuid.v4(); + const report = { + test_name: 'some name' + }; + const additionalInfo = { + lastScores: [90, 100, 100], + score: 97.5, + benchmarkThreshold: 90, + aggregatedReport: { + aggregate: { + key: 'value' + } + } + }; + const statsText = 'some text'; + statsFormatterStub.returns(statsText); + const expectedResult = `:rocket: *Test ${report.test_name} got a score of ${additionalInfo.score.toFixed(1)}` + + ` this is above the threshold of ${additionalInfo.benchmarkThreshold}. last 3 scores are: ${additionalInfo.lastScores.join()}` + + `.*\n${statsText}\n`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, uuid.v4(), testId, report, additionalInfo); + + expect(payload.text).to.be.equal(expectedResult); + }); + it(WEBHOOK_EVENT_TYPE_FAILED, function () { + const testId = uuid.v4(); + const report = { + test_name: 'some name', + environment: 'test' + }; + const additionalInfo = { + stats: { + data: 'data' + } + }; + const expectedResult = `😞 *Test with id: ${testId} Failed*.\n + test configuration:\n + environment: ${report.environment}\n + ${additionalInfo.stats.data}`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_FAILED, uuid.v4(), testId, report, additionalInfo); + + expect(payload.text).to.be.equal(expectedResult); + }); + it(WEBHOOK_EVENT_TYPE_IN_PROGRESS, function () { + const testId = uuid.v4(); + const report = { + test_name: 'some name' + }; + const expectedResult = `:hammer_and_wrench: *Test ${report.test_name} with id: ${testId} is in progress!*`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_IN_PROGRESS, uuid.v4(), testId, report); + + expect(payload.text).to.be.equal(expectedResult); + }); + it(WEBHOOK_EVENT_TYPE_API_FAILURE, function () { + const testId = uuid.v4(); + const report = { + test_name: 'some name' + }; + const expectedResult = `::boom:: *Test ${report.test_name} with id: ${testId} has encountered an API failure!* :skull:`; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_API_FAILURE, uuid.v4(), testId, report); + + expect(payload.text).to.be.equal(expectedResult); + }); + it('uknown event type -> expect error to be thrown', function () { + const unknownEventType = 'superUknownEventType'; + const expectedErrorMessage = `Unrecognized webhook event: ${unknownEventType}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`; + expect(webhooksFormatter.bind(null, EVENT_FORMAT_TYPE_SLACK, unknownEventType)).to.throw(expectedErrorMessage); + }); + }); + describe(EVENT_FORMAT_TYPE_JSON, function () { + WEBHOOK_EVENT_TYPES.forEach(webhookEventType => { + it(webhookEventType, function () { + const testId = uuid.v4(); + const jobId = uuid.v4(); + const report = { + ramp_to: 100, + test_name: 'some test name', + arrival_rate: 5, + duration: 120, + environment: 'test', + parallelism: 10 + }; + const additionalInfo = { + some: { + nested: { + value: ['Look', 'more', 'values'] + } + } + }; + const expectedResult = { + test_id: testId, + job_id: jobId, + event: webhookEventType, + additional_details: { + report, + ...additionalInfo + } + }; + + const payload = webhooksFormatter(EVENT_FORMAT_TYPE_JSON, webhookEventType, jobId, testId, report, additionalInfo); + + expect(payload).to.be.deep.equal(expectedResult); + }); + }); + it('uknown event type -> expect error to be thrown', function() { + const unknownEventType = 'superUknownEventType'; + const expectedErrorMessage = `Unrecognized webhook event: ${unknownEventType}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`; + expect(webhooksFormatter.bind(null, EVENT_FORMAT_TYPE_JSON, unknownEventType)).to.throw(expectedErrorMessage); + }); + }); + describe('Unknown format', function() { + it('should throw an error', function() { + const unknownFormat = 'some_random_format'; + expect(webhooksFormatter.bind(null, unknownFormat, WEBHOOK_EVENT_TYPE_STARTED)).to.throw(`Unrecognized webhook format: ${unknownFormat}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`); + }); + }); +}); From 56374b7dc53c35baf0074211f1bc794c08b4efa8 Mon Sep 17 00:00:00 2001 From: Daniel Hermon Date: Thu, 27 Aug 2020 21:02:12 +0300 Subject: [PATCH 34/38] test(webhooks): fix and add unit tests for notifier --- src/reports/models/notifier.js | 9 +- src/webhooks/models/webhooksFormatter.js | 2 +- .../reporter/models/notifier-test.js | 437 ++++++++++-------- .../models/reportWebhookSender-test.js | 67 --- .../sequelize/webhooksFormatter-test.js | 2 +- 5 files changed, 262 insertions(+), 255 deletions(-) delete mode 100644 tests/unit-tests/reporter/models/reportWebhookSender-test.js diff --git a/src/reports/models/notifier.js b/src/reports/models/notifier.js index f5df7a593..8e6343f40 100644 --- a/src/reports/models/notifier.js +++ b/src/reports/models/notifier.js @@ -29,7 +29,7 @@ module.exports.notifyIfNeeded = async (report, stats, reportBenchmark = {}) => { switch (stats.phase_status) { case constants.SUBSCRIBER_FAILED_STAGE: { logger.info(metadata, stats.error, 'handling error message'); - await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_FAILED, { report, stats }); + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_FAILED, report); break; } case constants.SUBSCRIBER_STARTED_STAGE: { @@ -77,9 +77,8 @@ async function handleFirstIntermediate(report, job) { if (!reportUtil.isAllRunnersInExpectedPhase(report, constants.SUBSCRIBER_FIRST_INTERMEDIATE_STAGE)) { return; } - // WHAT DO WE DO WITH BATCHES let aggregatedReport = await aggregateReportGenerator.createAggregateReport(report.test_id, report.report_id); - webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_IN_PROGRESS, { report, aggregatedReport }); + await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_IN_PROGRESS, report, { aggregatedReport }); } async function handleDone(report, job, reportBenchmark) { @@ -98,7 +97,7 @@ async function handleDone(report, job, reportBenchmark) { if (reportBenchmark.score && benchmarkThreshold) { const lastReports = await reportsManager.getReports(aggregatedReport.test_id); const lastScores = lastReports.slice(0, 3).filter(report => report.score).map(report => report.score.toFixed(1)); - const { event, icon } = reportBenchmark.score < benchmarkThreshold ? { event: WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, icon: ':sad_1:' } : { event: WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, icon: ':grin:' }; + const { event, icon } = reportBenchmark.score < benchmarkThreshold ? { event: WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, icon: ':cry:' } : { event: WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, icon: ':grin:' }; await webhooksManager.fireWebhookByEvent(job, event, report, { aggregatedReport, score: reportBenchmark.score, lastScores, benchmarkThreshold }, { icon }); } await webhooksManager.fireWebhookByEvent(job, WEBHOOK_EVENT_TYPE_FINISHED, report, { aggregatedReport, score: reportBenchmark.score }, { icon: ':rocket:' }); @@ -126,7 +125,7 @@ async function handleIntermediate(report, job) { } return accumulated; }, {}); - // if there are no stats that have a status code of >= 500, do nothing + // if there are no stats that have a status code of >= 500, do nothing if (Object.keys(accumulatedStatusCodesCounter).every(statusCode => statusCode < 500)) { return; } diff --git a/src/webhooks/models/webhooksFormatter.js b/src/webhooks/models/webhooksFormatter.js index e64c5b923..c6652ab87 100644 --- a/src/webhooks/models/webhooksFormatter.js +++ b/src/webhooks/models/webhooksFormatter.js @@ -26,7 +26,7 @@ function getThresholdSlackMessage(state, { testName, benchmarkThreshold, lastSco let icon = ':rocket:'; if (state === WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED) { resultText = 'below'; - icon = ':sad_1:'; + icon = ':cry:'; } return `${icon} *Test ${testName} got a score of ${score.toFixed(1)}` + ` this is ${resultText} the threshold of ${benchmarkThreshold}. ${lastScores.length > 0 ? `last 3 scores are: ${lastScores.join()}` : 'no last score to show'}` + diff --git a/tests/unit-tests/reporter/models/notifier-test.js b/tests/unit-tests/reporter/models/notifier-test.js index 35f5d365e..cca5e97cd 100644 --- a/tests/unit-tests/reporter/models/notifier-test.js +++ b/tests/unit-tests/reporter/models/notifier-test.js @@ -1,29 +1,38 @@ 'use strict'; - process.env.JOB_PLATFORM = 'DOCKER'; - let sinon = require('sinon'); let should = require('should'); + let logger = require('../../../../src/common/logger'); let notifier = require('../../../../src/reports/models/notifier'); let jobsManager = require('../../../../src/jobs/models/jobManager'); -let reportWebhookSender = require('../../../../src/reports/models/reportWebhookSender'); +let webhooksManager = require('../../../../src/webhooks/models/webhookManager'); let configHandler = require('../../../../src/configManager/models/configHandler'); -let statsFormatter = require('../../../../src/reports/models/statsFormatter'); +let statsFormatter = require('../../../../src/webhooks/models/statsFormatter'); let aggregateReportGenerator = require('../../../../src/reports/models/aggregateReportGenerator'); let reportEmailSender = require('../../../../src/reports/models/reportEmailSender'); const reportsManager = require('../../../../src/reports/models/reportsManager'); -let configConstants = require('../../../../src/common/consts').CONFIG; +const { + WEBHOOK_EVENT_TYPE_FAILED, + WEBHOOK_EVENT_TYPE_STARTED, + WEBHOOK_EVENT_TYPE_IN_PROGRESS, + WEBHOOK_EVENT_TYPE_FINISHED, + WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, + WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, + WEBHOOK_EVENT_TYPE_ABORTED, + WEBHOOK_EVENT_TYPE_API_FAILURE +} = require('../../../../src/common/consts'); +const { SUBSCRIBER_INTERMEDIATE_STAGE, SUBSCRIBER_FAILED_STAGE, SUBSCRIBER_STARTED_STAGE, SUBSCRIBER_FIRST_INTERMEDIATE_STAGE, SUBSCRIBER_DONE_STAGE, SUBSCRIBER_ABORTED_STAGE } = require('../../../../src/reports/utils/constants'); describe('Webhook/email notifier test ', () => { - let sandbox, loggerInfoStub, loggerWarnStub, reportWebhookSenderSendStub, + let sandbox, loggerInfoStub, loggerWarnStub, webhooksManagerFireWebhookStub, statsFormatterStub, jobsManagerStub, getConfigStub, aggregateReportGeneratorStub, reportEmailSenderStub, getReportsStub; before(() => { sandbox = sinon.sandbox.create(); loggerInfoStub = sandbox.stub(logger, 'info'); loggerWarnStub = sandbox.stub(logger, 'warn'); - reportWebhookSenderSendStub = sandbox.stub(reportWebhookSender, 'send'); + webhooksManagerFireWebhookStub = sandbox.stub(webhooksManager, 'fireWebhookByEvent'); statsFormatterStub = sandbox.stub(statsFormatter, 'getStatsFormatted'); jobsManagerStub = sandbox.stub(jobsManager, 'getJob'); getConfigStub = sandbox.stub(configHandler, 'getConfigValue'); @@ -40,13 +49,15 @@ describe('Webhook/email notifier test ', () => { sandbox.restore(); }); - it('Handing message with phase: error', async () => { - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'] - }); + it('handling message with phase: error', async () => { + const job = { + some: 'value', + more: 'keys' + }; + jobsManagerStub.resolves(job); let report = { environment: 'test', report_id: 'report_id', test_id: 'test_id' }; let stats = { - phase_status: 'error', + phase_status: SUBSCRIBER_FAILED_STAGE, error: { 'code': 500, 'message': 'fail to get test' @@ -55,13 +66,8 @@ describe('Webhook/email notifier test ', () => { }; await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(1); - reportWebhookSenderSendStub.args.should.containDeep([ - [ - '😞 *Test with id: test_id Failed*.\ntest configuration:\nenvironment: test\n{"message":"fail to get test"}', - ['http://www.zooz.com'] - ] - ]); + webhooksManagerFireWebhookStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.args[0].should.containDeep([ job, WEBHOOK_EVENT_TYPE_FAILED, report ]); loggerInfoStub.callCount.should.equal(1); loggerInfoStub.args.should.deepEqual([ [ @@ -78,11 +84,12 @@ describe('Webhook/email notifier test ', () => { ]); }); - describe('Handing message with phase: started_phase', () => { + describe('handling message with phase: started_phase', () => { it('parallelism is 2 and ramp to is defined, runners in correct phases ', async () => { - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com', 'http://www.zooz2.com'] - }); + const job = { + some: 'keys' + }; + jobsManagerStub.resolves(job); let report = { environment: 'test', report_id: 'report_id', @@ -94,29 +101,25 @@ describe('Webhook/email notifier test ', () => { ramp_to: 20, status: 'started', phase: '0', - subscribers: [{ phase_status: 'started_phase' }, { phase_status: 'started_phase' }] + subscribers: [{ phase_status: SUBSCRIBER_STARTED_STAGE }, { phase_status: SUBSCRIBER_STARTED_STAGE }] }; let stats = { - phase_status: 'started_phase', + phase_status: SUBSCRIBER_STARTED_STAGE, data: JSON.stringify({ 'message': 'fail to get test' }) }; await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(1); - reportWebhookSenderSendStub.args.should.containDeep([ - [ - '🤓 *Test some_test_name with id: test_id has started*.\n\n *test configuration:* environment: test duration: 10 seconds, arrival rate: 10 scenarios per second, number of runners: 2, ramp to: 20 scenarios per second', - ['http://www.zooz.com', 'http://www.zooz2.com'] - ] - ]); + webhooksManagerFireWebhookStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_STARTED, report]); loggerInfoStub.callCount.should.equal(1); }); it('parallelism is not defined, runner in corrects phase ', async () => { - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'] - }); + const job = { + some: 'keys' + }; + jobsManagerStub.resolves(job); let report = { environment: 'test', report_id: 'report_id', @@ -124,21 +127,16 @@ describe('Webhook/email notifier test ', () => { test_name: 'some_test_name', duration: 10, arrival_rate: 10, - subscribers: [{ phase_status: 'started_phase' }] + subscribers: [{ phase_status: SUBSCRIBER_STARTED_STAGE }] }; let stats = { - phase_status: 'started_phase', + phase_status: SUBSCRIBER_STARTED_STAGE, data: JSON.stringify({ 'message': 'fail to get test' }) }; await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(1); - reportWebhookSenderSendStub.args.should.containDeep([ - [ - '🤓 *Test some_test_name with id: test_id has started*.\n\n *test configuration:* environment: test duration: 10 seconds, arrival rate: 10 scenarios per second, number of runners: 1', - ['http://www.zooz.com'] - ] - ]); + webhooksManagerFireWebhookStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_STARTED, report]); loggerInfoStub.callCount.should.equal(1); }); }); @@ -158,23 +156,25 @@ describe('Webhook/email notifier test ', () => { ramp_to: 20, status: 'started', phase: '0', - subscribers: [{ phase_status: 'started_phase' }, { phase_status: 'not_started_phase' }] + subscribers: [{ phase_status: SUBSCRIBER_STARTED_STAGE }, { phase_status: 'not_started_phase' }] }; let stats = { - phase_status: 'started_phase' + phase_status: SUBSCRIBER_STARTED_STAGE }; await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(0); + webhooksManagerFireWebhookStub.callCount.should.equal(0); }); - it('Handing message with phase: first_intermediate', async () => { - aggregateReportGeneratorStub.resolves({}); + it('handling message with phase: first_intermediate', async () => { + const job = { + some: 'keys' + }; + const aggregatedReport = {}; + aggregateReportGeneratorStub.resolves(aggregatedReport); - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'] - }); + jobsManagerStub.resolves(job); let report = { environment: 'test', report_id: 'report_id', @@ -186,11 +186,11 @@ describe('Webhook/email notifier test ', () => { ramp_to: 20, status: 'started', phase: 0, - subscribers: [{ phase_status: 'first_intermediate' }, { phase_status: 'first_intermediate' }] + subscribers: [{ phase_status: SUBSCRIBER_FIRST_INTERMEDIATE_STAGE }, { phase_status: SUBSCRIBER_FIRST_INTERMEDIATE_STAGE }] }; let stats = { - phase_status: 'first_intermediate', + phase_status: SUBSCRIBER_FIRST_INTERMEDIATE_STAGE, data: JSON.stringify({}) }; @@ -198,20 +198,16 @@ describe('Webhook/email notifier test ', () => { await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(1); - reportWebhookSenderSendStub.args.should.containDeep([ - [ - '🤔 *Test some_test_name with id: test_id first batch of results arrived for phase 0.*\nmax: 1, min: 0.4, median: 0.7\n', - ['http://www.zooz.com'] - ] - ]); + webhooksManagerFireWebhookStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_IN_PROGRESS, report, { aggregatedReport }]); loggerInfoStub.callCount.should.equal(1); }); - it('Handing message with phase: intermediate, not first message', async () => { - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'] - }); + it('handling message with phase: intermediate, not first message', async () => { + const job = { + some: 'keys' + }; + jobsManagerStub.resolves(job); let report = { environment: 'test', report_id: 'report_id', @@ -222,30 +218,25 @@ describe('Webhook/email notifier test ', () => { parallelism: 5, ramp_to: 20, status: 'running', - phase: 0 + phase: 0, + subscribers: [{ phase_status: SUBSCRIBER_INTERMEDIATE_STAGE, last_stats: { codes: { 200: 15, 301: 13 } } }, { phase_status: SUBSCRIBER_INTERMEDIATE_STAGE, last_stats: { codes: { 200: 15, 301: 13 } } }] }; statsFormatterStub.returns('max: 1, min: 0.4, median: 0.7'); let stats = { - phase_status: 'intermediate', + phase_status: SUBSCRIBER_INTERMEDIATE_STAGE, data: JSON.stringify({}) }; await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(0); + webhooksManagerFireWebhookStub.callCount.should.equal(0); }); - it('Handing message with phase: done', async () => { - getConfigStub.resolves('test@predator.com'); - getConfigStub.withArgs(sinon.match('default_webhook_url')).resolves('http://www.webhook.com'); - getConfigStub.withArgs(sinon.match('default_email_address')).resolves('test@predator.com'); - - aggregateReportGeneratorStub.resolves({}); - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'], - emails: ['test2@predator.com'] - - }); + it('Handling message with phase: intermediate, having status codes with >= 500, should fire API_FAILURE webhooks flow', async function() { + const job = { + some: 'keys' + }; + jobsManagerStub.resolves(job); let report = { environment: 'test', report_id: 'report_id', @@ -253,141 +244,225 @@ describe('Webhook/email notifier test ', () => { test_name: 'some_test_name', duration: 10, arrival_rate: 10, - parallelism: 2, + parallelism: 5, ramp_to: 20, - status: 'started', + status: 'running', phase: 0, - subscribers: [{ phase_status: 'done' }, { phase_status: 'done' }] - + subscribers: [{ phase_status: SUBSCRIBER_INTERMEDIATE_STAGE, last_stats: { codes: { 200: 15, 301: 13, 500: 1 } } }, { phase_status: SUBSCRIBER_INTERMEDIATE_STAGE, last_stats: { codes: { 200: 15, 301: 13 } } }] }; + const accumulatedStatusCodesCounter = { 200: 30, 301: 26, 500: 1 }; + statsFormatterStub.returns('max: 1, min: 0.4, median: 0.7'); let stats = { - phase_status: 'done', + phase_status: SUBSCRIBER_INTERMEDIATE_STAGE, data: JSON.stringify({}) }; - statsFormatterStub.returns('max: 1, min: 0.4, median: 0.7'); - await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(1); - reportWebhookSenderSendStub.args.should.containDeep([ - [ - '😎 *Test some_test_name with id: test_id is finished.*\nmax: 1, min: 0.4, median: 0.7\n', - ['http://www.zooz.com'] - ] - ]); - - reportEmailSenderStub.callCount.should.equal(1); - reportEmailSenderStub.args[0][2].should.containDeep(['test@predator.com', 'test2@predator.com']); - loggerInfoStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_API_FAILURE, report, { accumulatedStatusCodesCounter }, { icon: ':skull:' }]); }); - it('Handing message with phase: done - score is less than threshold with 3 last scores', async () => { - getConfigStub.resolves('test@predator.com'); - getConfigStub.withArgs(configConstants.DEFAULT_WEBHOOK_URL).resolves('http://www.webhook.com'); - getConfigStub.withArgs(configConstants.DEFAULT_EMAIL_ADDRESS).resolves('test@predator.com'); - getConfigStub.withArgs(configConstants.BENCHMARK_THRESHOLD_WEBHOOK_URL).resolves('benchmarktest@predator.com'); - getConfigStub.withArgs(configConstants.BENCHMARK_THRESHOLD).resolves(10); - getReportsStub.resolves([{ score: 9.44556 }, { score: 8.34556 }, { score: 7.24556 }]); + describe('Handling messages with phase: done', function() { + it('should fire FINISHED webhook flow for unset benchmark threshold', async function() { + const job = { + some: 'keys', + emails: [] + }; + const score = 100; + const aggregatedReport = { + special: 'key' + }; + const report = { + environment: 'test', + report_id: 'report_id', + test_id: 'test_id', + test_name: 'some_test_name', + duration: 10, + arrival_rate: 10, + parallelism: 2, + ramp_to: 20, + status: 'started', + phase: 0, + subscribers: [{ phase_status: SUBSCRIBER_DONE_STAGE }, { phase_status: SUBSCRIBER_DONE_STAGE }] - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'], - emails: ['test2@predator.com'] + }; + let stats = { + phase_status: SUBSCRIBER_DONE_STAGE, + data: JSON.stringify({}) + }; + getConfigStub.resolves(undefined); + getConfigStub.withArgs(sinon.match('benchmark_threshold')).resolves(undefined); + aggregateReportGeneratorStub.resolves(aggregatedReport); + jobsManagerStub.resolves(job); + + await notifier.notifyIfNeeded(report, stats, { score }); + + webhooksManagerFireWebhookStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_FINISHED, report, { aggregatedReport, score }, { icon: ':rocket:' }]); + + reportEmailSenderStub.callCount.should.equal(0); }); - let report = { - environment: 'test', - report_id: 'report_id', - test_id: 'test_id', - test_name: 'some_test_name', - duration: 10, - arrival_rate: 10, - parallelism: 2, - ramp_to: 20, - status: 'started', - phase: 0, - subscribers: [{ phase_status: 'done' }, { phase_status: 'done' }] + it('should fire 2 webhooks flows, 1 with BENCHMARK_PASSED and FINISHED, should also take 3 last scores', async function() { + const job = { + some: 'keys', + emails: ['meow@catdomain.com'] + }; + const score = 97; + const aggregatedReport = { + special: 'key' + }; + const report = { + environment: 'test', + report_id: 'report_id', + test_id: 'test_id', + test_name: 'some_test_name', + duration: 10, + arrival_rate: 10, + parallelism: 2, + ramp_to: 20, + status: 'started', + phase: 0, + subscribers: [{ phase_status: SUBSCRIBER_DONE_STAGE }, { phase_status: SUBSCRIBER_DONE_STAGE }] - }; - let stats = { - phase_status: 'done', - data: JSON.stringify({}) - }; - statsFormatterStub.returns('max: 1, min: 0.4, median: 0.7'); - aggregateReportGeneratorStub.resolves(report); - await notifier.notifyIfNeeded(report, stats, { score: 8 }); + }; + let stats = { + phase_status: SUBSCRIBER_DONE_STAGE, + data: JSON.stringify({}) + }; + const reports = [{ score: 90 }, { score: 95 }, { score: 95.444 }, { score: 100 }]; + const benchmarkThreshold = 30; + const reportBenchmark = { score }; - reportWebhookSenderSendStub.args.should.containDeep([ - [ - ':sad_1: *Test some_test_name got a score of 8.0 this is below the threshold of 10. last 3 scores are: 9.4,8.3,7.2.*\nmax: 1, min: 0.4, median: 0.7\n' - ] - ]); - }); + getConfigStub.resolves(undefined); + getConfigStub.withArgs(sinon.match('benchmark_threshold')).resolves(benchmarkThreshold); + aggregateReportGeneratorStub.resolves(aggregatedReport); + jobsManagerStub.resolves(job); + getReportsStub.resolves(reports); - it('Handing message with phase: done - score is less than threshold with no 3 last scores', async () => { - getConfigStub.resolves('test@predator.com'); - getConfigStub.withArgs(configConstants.DEFAULT_WEBHOOK_URL).resolves('http://www.webhook.com'); - getConfigStub.withArgs(configConstants.DEFAULT_EMAIL_ADDRESS).resolves('test@predator.com'); - getConfigStub.withArgs(configConstants.BENCHMARK_THRESHOLD_WEBHOOK_URL).resolves('benchmarktest@predator.com'); - getConfigStub.withArgs(configConstants.BENCHMARK_THRESHOLD).resolves(10); - getReportsStub.resolves([]); + await notifier.notifyIfNeeded(report, stats, reportBenchmark); - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'], - emails: ['test2@predator.com'] + webhooksManagerFireWebhookStub.callCount.should.equal(2); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, report, { aggregatedReport, score, lastScores: ['90.0', '95.0', '95.4'], benchmarkThreshold }, { icon: ':grin:' }]); + webhooksManagerFireWebhookStub.args[1].should.containDeep([job, WEBHOOK_EVENT_TYPE_FINISHED, report, { aggregatedReport, score }, { icon: ':rocket:' }]); + reportEmailSenderStub.callCount.should.equal(1); + reportEmailSenderStub.args[0].should.containDeep([aggregatedReport, job, job.emails, reportBenchmark]); }); - let report = { - environment: 'test', - report_id: 'report_id', - test_id: 'test_id', - test_name: 'some_test_name', - duration: 10, - arrival_rate: 10, - parallelism: 2, - ramp_to: 20, - status: 'started', - phase: 0, - subscribers: [{ phase_status: 'done' }, { phase_status: 'done' }] + it('should fire 2 webhooks flows, 1 with BENCHMARK_FAILED and FINISHED', async function() { + const job = { + some: 'keys', + emails: ['meow@catdomain.com'] + }; + const score = 20; + const aggregatedReport = { + special: 'key' + }; + const report = { + environment: 'test', + report_id: 'report_id', + test_id: 'test_id', + test_name: 'some_test_name', + duration: 10, + arrival_rate: 10, + parallelism: 2, + ramp_to: 20, + status: 'started', + phase: 0, + subscribers: [{ phase_status: SUBSCRIBER_DONE_STAGE }, { phase_status: SUBSCRIBER_DONE_STAGE }] - }; - let stats = { - phase_status: 'done', - data: JSON.stringify({}) - }; - statsFormatterStub.returns('max: 1, min: 0.4, median: 0.7'); - aggregateReportGeneratorStub.resolves(report); - await notifier.notifyIfNeeded(report, stats, { score: 8 }); + }; + let stats = { + phase_status: SUBSCRIBER_DONE_STAGE, + data: JSON.stringify({}) + }; + const reports = [{ score: 20 }, { score: 20 }, { score: 20.6 }, { score: 20 }]; + const benchmarkThreshold = 30; + const reportBenchmark = { score }; - reportWebhookSenderSendStub.args.should.containDeep([ - [ - ':sad_1: *Test some_test_name got a score of 8.0 this is below the threshold of 10. no last score to show.*\nmax: 1, min: 0.4, median: 0.7\n' - ] - ]); - }); + getConfigStub.resolves(undefined); + getConfigStub.withArgs(sinon.match('benchmark_threshold')).resolves(benchmarkThreshold); + aggregateReportGeneratorStub.resolves(aggregatedReport); + jobsManagerStub.resolves(job); + getReportsStub.resolves(reports); - it('Handing message with phase: aborted', async () => { - jobsManagerStub.resolves({ - webhooks: ['http://www.zooz.com'] + await notifier.notifyIfNeeded(report, stats, reportBenchmark); + + webhooksManagerFireWebhookStub.callCount.should.equal(2); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, report, { aggregatedReport, score, lastScores: ['20.0', '20.0', '20.6'], benchmarkThreshold }, { icon: ':cry:' }]); + webhooksManagerFireWebhookStub.args[1].should.containDeep([job, WEBHOOK_EVENT_TYPE_FINISHED, report, { aggregatedReport, score }, { icon: ':rocket:' }]); + + reportEmailSenderStub.callCount.should.equal(1); + reportEmailSenderStub.args[0].should.containDeep([aggregatedReport, job, job.emails, reportBenchmark]); + }); + it('should fire 2 webhooks flows, 1 with BENCHMARK_FAILED and FINISHED with no 3 last scores', async function() { + const job = { + some: 'keys', + emails: ['meow@catdomain.com'] + }; + const score = 20; + const aggregatedReport = { + special: 'key' + }; + const report = { + environment: 'test', + report_id: 'report_id', + test_id: 'test_id', + test_name: 'some_test_name', + duration: 10, + arrival_rate: 10, + parallelism: 2, + ramp_to: 20, + status: 'started', + phase: 0, + subscribers: [{ phase_status: SUBSCRIBER_DONE_STAGE }, { phase_status: SUBSCRIBER_DONE_STAGE }] + + }; + let stats = { + phase_status: SUBSCRIBER_DONE_STAGE, + data: JSON.stringify({}) + }; + const reports = []; + const benchmarkThreshold = 30; + const reportBenchmark = { score }; + + getConfigStub.resolves(undefined); + getConfigStub.withArgs(sinon.match('benchmark_threshold')).resolves(benchmarkThreshold); + aggregateReportGeneratorStub.resolves(aggregatedReport); + jobsManagerStub.resolves(job); + getReportsStub.resolves(reports); + + await notifier.notifyIfNeeded(report, stats, reportBenchmark); + + webhooksManagerFireWebhookStub.callCount.should.equal(2); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, report, { aggregatedReport, score, lastScores: [], benchmarkThreshold }, { icon: ':cry:' }]); + webhooksManagerFireWebhookStub.args[1].should.containDeep([job, WEBHOOK_EVENT_TYPE_FINISHED, report, { aggregatedReport, score }, { icon: ':rocket:' }]); + + reportEmailSenderStub.callCount.should.equal(1); + reportEmailSenderStub.args[0].should.containDeep([aggregatedReport, job, job.emails, reportBenchmark]); }); + }); + + it('handling message with phase: aborted', async () => { + const job = { + some: 'job', + key: 'wow' + }; + jobsManagerStub.resolves(job); let report = { test_name: 'test_name', environment: 'test', report_id: 'report_id', test_id: 'test_id' }; let stats = { - phase_status: 'aborted', + phase_status: SUBSCRIBER_ABORTED_STAGE, data: JSON.stringify({ 'message': 'fail to get test' }) }; await notifier.notifyIfNeeded(report, stats); - reportWebhookSenderSendStub.callCount.should.equal(1); - reportWebhookSenderSendStub.args.should.containDeep([ - [ - '😢 *Test test_name with id: test_id was aborted.*\n', - ['http://www.zooz.com'] - ] - ]); + webhooksManagerFireWebhookStub.callCount.should.equal(1); + webhooksManagerFireWebhookStub.args[0].should.containDeep([job, WEBHOOK_EVENT_TYPE_ABORTED, report]); loggerInfoStub.callCount.should.equal(1); }); - it('Handing message with unknown phase', async () => { + it('handling message with unknown phase', async () => { let report = { test_name: 'test_name', environment: 'test', report_id: 'report_id', test_id: 'test_id' }; let stats = { phase_status: 'unknown', diff --git a/tests/unit-tests/reporter/models/reportWebhookSender-test.js b/tests/unit-tests/reporter/models/reportWebhookSender-test.js deleted file mode 100644 index 3cdcfaeff..000000000 --- a/tests/unit-tests/reporter/models/reportWebhookSender-test.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -process.env.JOB_PLATFORM = 'DOCKER'; - -let sinon = require('sinon'); -let should = require('should'); -let logger = require('../../../../src/common/logger'); -let reportWebhookSender = require('../../../../src/reports/models/reportWebhookSender'); -let reportsManager = require('../../../../src/reports/models/reportsManager'); -let request = require('request-promise-native'); - -const EXPECTED_REQUEST_SENDING_WEBHOOKS = [ - [ - { - url: 'http://a.com', - body: { - text: 'some message', - icon_emoji: ':muscle:', - username: 'reporter' - }, - json: true - } - ], - [ - { - url: 'http://b.com', - body: { - text: 'some message', - icon_emoji: ':muscle:', - username: 'reporter' - }, - json: true - } - ] -]; -describe('Report webhook sender test', () => { - let sandbox, reportsManagerGetReportStub, loggerErrorStub, requestPostStub; - - before(() => { - sandbox = sinon.sandbox.create(); - reportsManagerGetReportStub = sandbox.stub(reportsManager, 'getReport'); - loggerErrorStub = sandbox.stub(logger, 'error'); - requestPostStub = sandbox.stub(request, 'post'); - }); - - beforeEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - }); - - it('Send webhooks successfully when webhooks not passed but read from report', async () => { - requestPostStub.resolves({ status: 201 }); - const JOB = { webhooks: ['http://a.com', 'http://b.com'] }; - await reportWebhookSender.send(JOB.webhooks , 'some message'); - requestPostStub.callCount.should.equal(2); - requestPostStub.args.should.containDeep(EXPECTED_REQUEST_SENDING_WEBHOOKS); - }); - - it('No webhooks configured to be send', async () => { - const JOB = {webhooks: []}; - await reportWebhookSender.send(JOB.webhooks, 'some message'); - requestPostStub.callCount.should.equal(0); - }); -}); \ No newline at end of file diff --git a/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js b/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js index 7b7c71a8f..7b52d0502 100644 --- a/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js +++ b/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js @@ -102,7 +102,7 @@ describe('webhooksFormatter', function () { }; const statsText = 'some text'; statsFormatterStub.returns(statsText); - const expectedResult = `:sad_1: *Test ${report.test_name} got a score of ${additionalInfo.score.toFixed(1)}` + + const expectedResult = `:cry: *Test ${report.test_name} got a score of ${additionalInfo.score.toFixed(1)}` + ` this is below the threshold of ${additionalInfo.benchmarkThreshold}. last 3 scores are: ${additionalInfo.lastScores.join()}` + `.*\n${statsText}\n`; From e3470fcd71bac3d09c5258b39bf9900c4f9c4898 Mon Sep 17 00:00:00 2001 From: syncush Date: Fri, 28 Aug 2020 12:52:24 +0300 Subject: [PATCH 35/38] refactor!(server): drop Cassandra support --- .circleci/config.yml | 10 - .github/ISSUE_TEMPLATE/bug_report.md | 2 +- README.md | 2 +- docs/devguide/docs/about.md | 4 +- docs/devguide/docs/configuration.md | 12 +- docs/devguide/docs/index.html.bk | 48 +- docs/devguide/docs/project.mobirise | 4 +- package-lock.json | 1686 +---------------- package.json | 2 - src/config/databaseConfig.js | 13 - .../database/cassandra/cassandraConnector.js | 75 - .../models/database/databaseConnector.js | 4 +- src/database/cassandra-handler/cassandra.js | 71 - .../cassandra-handler/cassandraMigration.js | 247 --- .../cassandra_config_template.json | 18 - .../10__add_file_id_test_table.cql | 1 - .../init-scripts/11__create_files_table.cql | 4 - .../12__drop_last_reports_view.cql | 1 - .../13__create_last_reports_table.cql | 17 - .../14__add_enabled_jobs_table_.cql | 1 - .../14__create_processors_table.cql | 12 - .../15__create_processors_mapping_table.cql | 6 - .../17__processors_exported_functions.cql | 1 - .../18__bench_mark_data_tests.cql | 4 - .../19__bench_mark_data_reports.cql | 8 - .../init-scripts/1__create_jobs_table.cql | 15 - .../init-scripts/20__csv_file.cql | 3 - .../2__create_reports_summary_table.cql | 14 - .../3__create_reports_stats_table.cql | 10 - .../4__create_reports_subscribers_table.cql | 7 - ...5__create_last_reports_view_deprecated.cql | 5 - .../init-scripts/6__create_tests.cql | 12 - .../init-scripts/7__create_config_table.cql | 4 - .../init-scripts/8__dsl_table.cql | 7 - .../9__add_proxy_and_debug_jobs_table_.cql | 3 - src/database/database.js | 7 +- src/env.js | 11 +- src/files/models/database.js | 7 +- .../database/cassandra/cassandraConnector.js | 48 - .../database/cassandra/cassandraConnector.js | 98 - src/jobs/models/database/databaseConnector.js | 6 +- .../database/cassandra/cassandraConnector.js | 114 -- .../models/database/databaseConnector.js | 4 +- .../database/cassandra/cassandraConnector.js | 244 --- src/reports/models/databaseConnector.js | 4 +- src/tests/models/database.js | 7 +- .../database/cassandra/cassandraConnector.js | 153 -- src/tests/models/dsl.js | 6 +- .../database/cassandra/cassandraConnector.js | 31 - .../models/database/databaseConnector.js | 5 +- .../configurations/cassandraConfiguration.sh | 8 - tests/configurations/dockerRun.sh | 25 +- tests/integration-tests/runLocal.sh | 3 - .../cassandraHandler-test.js | 509 ----- .../configManager/cassandra/cassandra-test.js | 128 -- .../configManager/configHandler-test.js | 25 +- .../configHandlerEnvVaribles-test.js | 6 +- .../sequelize/sequelizeConnector-test.js | 4 +- tests/unit-tests/env-test.js | 2 +- .../files/models/cassandraConnector-test.js | 79 - .../unit-tests/files/models/database-test.js | 61 - .../jobs/cassandra/cassandra-test.js | 230 --- .../processors/cassandra/cassandra-test.js | 242 --- .../reporter/cassandra/cassandra-test.js | 385 ---- .../models/finalReportGenerator-test.js | 6 +- .../tests/models/cassandraConnector-test.js | 566 ------ .../unit-tests/tests/models/database-test.js | 95 - 67 files changed, 165 insertions(+), 5287 deletions(-) delete mode 100644 src/configManager/models/database/cassandra/cassandraConnector.js delete mode 100644 src/database/cassandra-handler/cassandra.js delete mode 100644 src/database/cassandra-handler/cassandraMigration.js delete mode 100644 src/database/cassandra-handler/cassandra_config_template.json delete mode 100644 src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/11__create_files_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql delete mode 100644 src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql delete mode 100644 src/database/cassandra-handler/init-scripts/14__create_processors_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql delete mode 100644 src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql delete mode 100644 src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql delete mode 100644 src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/20__csv_file.cql delete mode 100644 src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql delete mode 100644 src/database/cassandra-handler/init-scripts/6__create_tests.cql delete mode 100644 src/database/cassandra-handler/init-scripts/7__create_config_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/8__dsl_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql delete mode 100644 src/files/models/database/cassandra/cassandraConnector.js delete mode 100644 src/jobs/models/database/cassandra/cassandraConnector.js delete mode 100644 src/processors/models/database/cassandra/cassandraConnector.js delete mode 100644 src/reports/models/database/cassandra/cassandraConnector.js delete mode 100644 src/tests/models/database/cassandra/cassandraConnector.js delete mode 100644 src/webhooks/models/database/cassandra/cassandraConnector.js delete mode 100755 tests/configurations/cassandraConfiguration.sh delete mode 100644 tests/unit-tests/cassandra-handler/cassandraHandler-test.js delete mode 100644 tests/unit-tests/configManager/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/files/models/cassandraConnector-test.js delete mode 100644 tests/unit-tests/files/models/database-test.js delete mode 100644 tests/unit-tests/jobs/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/processors/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/reporter/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/tests/models/cassandraConnector-test.js delete mode 100644 tests/unit-tests/tests/models/database-test.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 0cb30450c..fa85a1fb8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,10 +33,6 @@ jobs: integration-tests: docker: - image: circleci/node:12.16 - - image: circleci/cassandra:3.10 - environment: - MAX_HEAP_SIZE: 2048m - HEAP_NEWSIZE: 512m - image: mailhog/mailhog - image: mysql:5.7 environment: @@ -58,12 +54,6 @@ jobs: environment: DATABASE_TYPE: sqlite JOB_PLATFORM: docker - - run: - name: Integration tests with kubernetes and cassandra configuration - command: npm run integration-tests - environment: - DATABASE_TYPE: cassandra - JOB_PLATFORM: kubernetes - run: name: Integration tests with kubernetes and mysql configuration command: npm run integration-tests diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 42f4c7ad1..d9d9ba482 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,7 +26,7 @@ If applicable, add screenshots to help explain your problem. **Version:** - Predator: [e.g. 1.0.8] - Predator-runner [e.g. 1.0.5] - - Database [e.g. cassandra] + - Database [e.g. sqlite] **Additional context** Add any other context about the problem here. diff --git a/README.md b/README.md index c2f805f32..082942a07 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ |-------------------------------- |:------------------:|:---------| | Distributed Load | :sparkle: |Predator supports an unlimited number of load generators that produce multiple load runners concurrently. | Rich UI | :sparkle: |Predator offers a rich UI where you can write tests, run them and compare results. -| Reports && Tests Persistence | :sparkle: |Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE. +| Reports && Tests Persistence | :sparkle: |Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE. | Real time reports | :sparkle: |Predator aggregates all concurrent runs into a single beautiful report in real time (latency, rps, status codes and more). | CSV Datasets | :sparkle: |Predator support uploading files like csv to provide dataset for test inputs | Scheduled runs | :sparkle: |Predator can run recurring tests using cron expressions. diff --git a/docs/devguide/docs/about.md b/docs/devguide/docs/about.md index 01253769d..e8e802363 100644 --- a/docs/devguide/docs/about.md +++ b/docs/devguide/docs/about.md @@ -18,7 +18,7 @@ Predator is a performance platform that can be configured to automatically load - **One click installation**: Predator can be installed with just one click in Kubernetes and DC/OS, or on any other machine running Docker. -- **Supports 5 Different databases**: Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE. +- **Supports 5 Different databases**: Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE. - **Scheduled jobs**: Predator can run recurring tests using cron expressions. @@ -37,5 +37,3 @@ load engine to fire the requests. The schema for creating tests via the Predator ## Feature Comparison ![Screenshot](images/features.png) - - diff --git a/docs/devguide/docs/configuration.md b/docs/devguide/docs/configuration.md index b06ebfe61..7cc316efc 100644 --- a/docs/devguide/docs/configuration.md +++ b/docs/devguide/docs/configuration.md @@ -22,7 +22,7 @@ Below are variables Predator can be configured with. ## Database | Environment Variable | Description | Configurable from UI/API | Default value | |---------------------- |--------------------------------------------------------------------------------- |-------------------------- |--------------- | -| DATABASE_TYPE | Database to integrate Predator with [Cassandra, Postgres, MySQL, MSSQL, SQLITE] | x | SQLITE | +| DATABASE_TYPE | Database to integrate Predator with [Postgres, MySQL, MSSQL, SQLITE] | x | SQLITE | | DATABASE_NAME | Database/Keyspace name | x | | | DATABASE_ADDRESS | Database address | x | | | DATABASE_USERNAME | Database username | x | | @@ -30,14 +30,6 @@ Below are variables Predator can be configured with. Additional parameters for the following chosen databases: -#### Cassandra -| Environment Variable | Configurable from UI/API | Default value | -|------------------------------ |-------------------------- |---------------- | -| CASSANDRA_REPLICATION_FACTOR | x | 1 | -| CASSANDRA_CONSISTENCY | x | localQuorum | -| CASSANDRA_KEY_SPACE_STRATEGY | x | SimpleStrategy | -| CASSANDRA_LOCAL_DATA_CENTER | x | | - #### SQLITE | Environment Variable | Description | Configurable from UI/API | Default value | |---------------------- |------------------ |-------------------------- |--------------- | @@ -109,4 +101,4 @@ Additional parameters for the following chosen databases: | SMTP_PASSWORD | smtp_server.password | SMTP password | ✓ | | | SMTP_TIMEOUT | smtp_server.timeout | How many milliseconds to wait for the connection to establish to SMTP server | ✓ | 200 | | SMTP_SECURE | smtp_server.secure | if true the connection will use TLS when connecting to server. [Nodemailer SMTP options](https://nodemailer.com/smtp/) | ✓ | false | -| SMTP_REJECT_UNAUTH_CERTS | smtp_server.rejectUnauthCerts | should fail or succeed on unauthorized certificate | ✓ | false | \ No newline at end of file +| SMTP_REJECT_UNAUTH_CERTS | smtp_server.rejectUnauthCerts | should fail or succeed on unauthorized certificate | ✓ | false | diff --git a/docs/devguide/docs/index.html.bk b/docs/devguide/docs/index.html.bk index f5306ecfe..1fb17b15b 100644 --- a/docs/devguide/docs/index.html.bk +++ b/docs/devguide/docs/index.html.bk @@ -28,28 +28,28 @@ - + @@ -162,7 +162,7 @@

Supports 5 storage backends

-

Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE.

+

Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE.

@@ -273,4 +273,4 @@ - \ No newline at end of file + diff --git a/docs/devguide/docs/project.mobirise b/docs/devguide/docs/project.mobirise index 7590e2c56..abcf3dea8 100644 --- a/docs/devguide/docs/project.mobirise +++ b/docs/devguide/docs/project.mobirise @@ -506,7 +506,7 @@ } }, "_name": "features5", - "_customHTML": "
\n \n \n \n \n \n \n \n \n \n\n
\n \n \n \n
\n \n \n \n \n
\n\n
\n
\n
\n
\n\n
\n
\n \n
\n
\n

\n Built for the cloud

\n

Predator is built to take advantage of Kubernetes and DC/OS. It’s integrated with those platforms and is able to manage the load generator lifecycles by itself.

\n
\n
\n\n
1\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n Distributed load

\n

Predator supports an unlimited number of load generators that produce multiple load runners concurrently. Predator will aggregate the result of those runners in real-time into a beautiful report.

\n
\n
\n\n
2\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n One click installation

\n

Predator can be installed with just one click in Kubernetes and DC/OS, or on any other machine running Docker.

\n
\n
\n\n
3\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

Supports 5 storage backends

\n

Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE.

\n
\n
\n
\n
\n
", + "_customHTML": "
\n \n \n \n \n \n \n \n \n \n\n
\n \n \n \n
\n \n \n \n \n
\n\n
\n
\n
\n
\n\n
\n
\n \n
\n
\n

\n Built for the cloud

\n

Predator is built to take advantage of Kubernetes and DC/OS. It’s integrated with those platforms and is able to manage the load generator lifecycles by itself.

\n
\n
\n\n
1\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n Distributed load

\n

Predator supports an unlimited number of load generators that produce multiple load runners concurrently. Predator will aggregate the result of those runners in real-time into a beautiful report.

\n
\n
\n\n
2\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n One click installation

\n

Predator can be installed with just one click in Kubernetes and DC/OS, or on any other machine running Docker.

\n
\n
\n\n
3\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

Supports 5 storage backends

\n

Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE.

\n
\n
\n
\n
\n
", "_cid": "rqyVDU3s7P", "_anchor": "features5-7", "_protectedParams": [], @@ -1309,4 +1309,4 @@ ] } } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 65bfbd6f0..cc7f35c3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ }, "@babel/generator": { "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fgenerator/-/generator-7.9.4.tgz", "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { @@ -101,7 +101,7 @@ }, "@babel/helper-validator-identifier": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", "dev": true }, @@ -149,7 +149,7 @@ }, "@babel/parser": { "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fparser/-/parser-7.9.4.tgz", "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", "dev": true }, @@ -164,7 +164,7 @@ }, "@babel/template": { "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftemplate/-/template-7.8.6.tgz", "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { @@ -175,7 +175,7 @@ }, "@babel/traverse": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftraverse/-/traverse-7.9.0.tgz", "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { @@ -209,7 +209,7 @@ }, "@babel/types": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftypes/-/types-7.9.0.tgz", "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { @@ -522,13 +522,6 @@ } } }, - "@commitlint/execute-rule": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-9.0.1.tgz", - "integrity": "sha512-fxnLadXs59qOBE9dInfQjQ4DmbGToQ0NjfqqmN6N8qS+KsCecO6N0mMUrC95et9xTeimFRr+0l9UMfmRVHNS/w==", - "dev": true, - "optional": true - }, "@commitlint/format": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-9.1.2.tgz", @@ -641,114 +634,6 @@ } } }, - "@commitlint/load": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-9.0.1.tgz", - "integrity": "sha512-6ix/pUjVAggmDLTcnpyk0bgY3H9UBBTsEeFvTkHV+WQ6LNIxsQk8SwEOEZzWHUqt0pxqMQeiUgYeSZsSw2+uiw==", - "dev": true, - "optional": true, - "requires": { - "@commitlint/execute-rule": "^9.0.1", - "@commitlint/resolve-extends": "^9.0.1", - "@commitlint/types": "^9.0.1", - "chalk": "3.0.0", - "cosmiconfig": "^6.0.0", - "lodash": "^4.17.15", - "resolve-from": "^5.0.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, - "optional": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "optional": 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, - "optional": true - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "optional": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "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, - "optional": true - }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "optional": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "optional": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "@commitlint/message": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-9.1.2.tgz", @@ -789,19 +674,6 @@ } } }, - "@commitlint/resolve-extends": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-9.0.1.tgz", - "integrity": "sha512-o6Lya2ILg1tEfWatS5x8w4ImvDzwb1whxsr2c/cxVCFqLF4hxHHHniZ0NJ+HFhYa1kBsYeKlD1qn9fHX5Y1+PQ==", - "dev": true, - "optional": true, - "requires": { - "import-fresh": "^3.0.0", - "lodash": "^4.17.15", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - } - }, "@commitlint/rules": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-9.1.2.tgz", @@ -873,13 +745,6 @@ } } }, - "@commitlint/types": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.0.1.tgz", - "integrity": "sha512-wo2rHprtDzTHf4tiSxavktJ52ntiwmg7eHNGFLH38G1of8OfGVwOc1sVbpM4jN/HK/rCMhYOi6xzoPqsv0537A==", - "dev": true, - "optional": true - }, "@js-joda/core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-2.0.0.tgz", @@ -925,11 +790,6 @@ "resolved": "http://npm.zooz.co:8083/@types%2fcolor-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, "@types/minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", @@ -1030,11 +890,6 @@ "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", "dev": true }, - "adm-zip": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", - "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==" - }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", @@ -1061,7 +916,7 @@ }, "ansi-colors": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "resolved": "http://npm.zooz.co:8083/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, @@ -1104,7 +959,7 @@ }, "anymatch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { @@ -1114,7 +969,7 @@ "dependencies": { "normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1265,24 +1120,6 @@ "sprintf-js": "~1.0.2" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -1317,12 +1154,6 @@ "is-string": "^1.0.5" } }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "array.prototype.flat": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", @@ -1454,12 +1285,6 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -1481,12 +1306,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -1520,61 +1339,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "base64-arraybuffer": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", @@ -1603,7 +1367,7 @@ }, "binary-extensions": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, @@ -1751,7 +1515,7 @@ }, "braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "resolved": "http://npm.zooz.co:8083/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { @@ -1836,29 +1600,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cachedir": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.2.0.tgz", - "integrity": "sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==", - "dev": true - }, "caching-transform": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", @@ -1974,39 +1715,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "cassandra-driver": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/cassandra-driver/-/cassandra-driver-4.3.1.tgz", - "integrity": "sha512-4Yuf9UkmwidiXKdAb4AgkC92CcUhP4hNUNaMNnKMGA3MRMQj2ZCEL6BR0/PrMPkYGC5faMPHJ4YzWAu3FFvM6g==", - "requires": { - "@types/long": "^4.0.0", - "@types/node": ">=4", - "adm-zip": "^0.4.13", - "long": "^2.2.0" - } - }, - "cassandra-migration": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cassandra-migration/-/cassandra-migration-2.7.0.tgz", - "integrity": "sha512-TVOLANJnURNXxLgSyoyKeYoAXqrbUJdCizOsh8riCqe5wwYM6m504lOwPgPMP9rnOVx8ImX1w96GpiQwWdLB3Q==", - "requires": { - "cassandra-driver": "^3.3.0", - "commander": "^2.9", - "durations": "^3.4.1", - "lodash": "^4.13.1", - "q": "^1.4" - }, - "dependencies": { - "cassandra-driver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/cassandra-driver/-/cassandra-driver-3.6.0.tgz", - "integrity": "sha512-CkN3V+oPaF5RvakUjD3uUjEm8f6U8S0aT1+YqeQsVT3UDpPT2K8SOdNDEHA1KjamakHch6zkDgHph1xWyqBGGw==", - "requires": { - "long": "^2.2.0" - } - } - } - }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -2065,7 +1773,7 @@ }, "chokidar": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "resolved": "http://npm.zooz.co:8083/chokidar/-/chokidar-3.3.0.tgz", "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { @@ -2081,13 +1789,13 @@ "dependencies": { "is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-glob": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { @@ -2096,7 +1804,7 @@ }, "normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -2112,29 +1820,6 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "cli-boxes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", @@ -2227,16 +1912,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2266,159 +1941,6 @@ "graceful-readlink": ">= 1.0.0" } }, - "commitizen": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.1.2.tgz", - "integrity": "sha512-LBxTQKHbVgroMz9ohpm86N+GfJobonGyvDc3zBGdZazbwCLz2tqLa48Rf2TnAdKx7/06W1i1R3SXUt5QW97qVQ==", - "dev": true, - "requires": { - "cachedir": "2.2.0", - "cz-conventional-changelog": "3.2.0", - "dedent": "0.7.0", - "detect-indent": "6.0.0", - "find-node-modules": "2.0.0", - "find-root": "1.1.0", - "fs-extra": "8.1.0", - "glob": "7.1.4", - "inquirer": "6.5.0", - "is-utf8": "^0.2.1", - "lodash": "4.17.15", - "minimist": "1.2.5", - "strip-bom": "4.0.0", - "strip-json-comments": "3.0.1" - }, - "dependencies": { - "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==", - "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==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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" - } - }, - "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" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "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" - } - }, - "inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", - "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.12", - "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" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "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" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, "commitlint": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/commitlint/-/commitlint-9.0.1.tgz", @@ -3097,9 +2619,9 @@ "dev": true }, "conventional-commit-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", - "integrity": "sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-2.3.0.tgz", + "integrity": "sha512-6iB39PrcGYdz0n3z31kj6/Km6mK9hm9oMRhwcLnKxE7WNoeRKZbTAobliKrbYZ5jqyCvtcVEfjCiaEzhL3AVmQ==", "dev": true }, "conventional-commits-filter": { @@ -3677,12 +3199,6 @@ "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", "dev": true }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, "copy-dir": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-0.3.0.tgz", @@ -3880,49 +3396,16 @@ } }, "cz-conventional-changelog": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.2.0.tgz", - "integrity": "sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-2.1.0.tgz", + "integrity": "sha1-L0vHOQ4yROTfKT5ro1Hkx0Cnx2Q=", "dev": true, "requires": { - "@commitlint/load": ">6.1.1", - "chalk": "^2.4.1", - "commitizen": "^4.0.3", - "conventional-commit-types": "^3.0.0", + "conventional-commit-types": "^2.0.0", "lodash.map": "^4.5.1", - "longest": "^2.0.1", + "longest": "^1.0.1", + "right-pad": "^1.0.1", "word-wrap": "^1.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "d": { @@ -4010,18 +3493,6 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==" }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -4079,47 +3550,6 @@ "object-keys": "^1.0.12" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4145,12 +3575,6 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true - }, "detect-indent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", @@ -4319,11 +3743,6 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, - "durations": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/durations/-/durations-3.4.2.tgz", - "integrity": "sha512-V/lf7y33dGaypZZetVI1eu7BmvkbC4dItq12OElLRpKuaU5JxQstV2zHwLv8P7cNbQ+KL1WD80zMCTx5dNC4dg==" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -5098,65 +4517,21 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "requires": { "d": "1", - "es5-ext": "~0.10.14" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "es5-ext": "~0.10.14" } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { - "homedir-polyfill": "^1.0.1" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "express": { @@ -5267,27 +4642,6 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -5310,71 +4664,6 @@ } } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -5433,7 +4722,7 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { @@ -5497,22 +4786,6 @@ } } }, - "find-node-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.0.0.tgz", - "integrity": "sha512-8MWIBRgJi/WpjjfVXumjPKCtmQ10B+fjx6zmSA+770GMJirLhWIzg8l763rhjl9xaeaHbnxPNRQKq2mgMhr+aw==", - "dev": true, - "requires": { - "findup-sync": "^3.0.0", - "merge": "^1.2.1" - } - }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -5522,35 +4795,6 @@ "locate-path": "^3.0.0" } }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "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-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" - } - } - } - }, "flat": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", @@ -5636,12 +4880,6 @@ } } }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, "foreground-child": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", @@ -5700,15 +4938,6 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.12.tgz", "integrity": "sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA==" }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -5758,7 +4987,7 @@ }, "fsevents": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "resolved": "http://npm.zooz.co:8083/fsevents/-/fsevents-2.1.2.tgz", "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true @@ -6074,12 +5303,6 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -6246,30 +5469,6 @@ "ini": "^1.3.4" } }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -6407,58 +5606,6 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "hasha": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", @@ -6470,19 +5617,10 @@ }, "he": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "resolved": "http://npm.zooz.co:8083/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", @@ -6896,26 +6034,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", @@ -6929,7 +6047,7 @@ }, "is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { @@ -6959,62 +6077,17 @@ "ci-info": "^1.5.0" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-date-object": { "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==" }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", @@ -7022,7 +6095,7 @@ }, "is-finite": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", "dev": true }, @@ -7063,7 +6136,7 @@ }, "is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, @@ -7169,12 +6242,6 @@ "is-invalid-path": "^0.1.0" } }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", @@ -7524,7 +6591,7 @@ }, "kind-of": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "resolved": "http://npm.zooz.co:8083/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "latest-version": { @@ -7743,15 +6810,10 @@ "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", "dev": true }, - "long": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", - "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=" - }, "longest": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-2.0.1.tgz", - "integrity": "sha1-eB4YMpaqlPbU2RbcM10NF676I/g=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true }, "loose-envify": { @@ -7812,27 +6874,12 @@ "pify": "^3.0.0" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, "map-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, "mathjs": { "version": "5.10.3", "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-5.10.3.tgz", @@ -7930,141 +6977,30 @@ } } }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - } - }, - "mersenne": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" } }, + "mersenne": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -8104,7 +7040,7 @@ }, "minimist": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { @@ -8150,27 +7086,6 @@ "minipass": "^2.9.0" } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mkdir-p": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mkdir-p/-/mkdir-p-0.0.7.tgz", @@ -8186,7 +7101,7 @@ }, "mocha": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-7.1.1.tgz", "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { @@ -8218,7 +7133,7 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -8227,7 +7142,7 @@ }, "chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -8238,7 +7153,7 @@ "dependencies": { "supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -8249,7 +7164,7 @@ }, "debug": { "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "resolved": "http://npm.zooz.co:8083/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { @@ -8258,7 +7173,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "resolved": "http://npm.zooz.co:8083/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { @@ -8272,7 +7187,7 @@ }, "log-symbols": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/log-symbols/-/log-symbols-3.0.0.tgz", "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { @@ -8281,7 +7196,7 @@ }, "mkdirp": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "resolved": "http://npm.zooz.co:8083/mkdirp/-/mkdirp-0.5.3.tgz", "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { @@ -8290,13 +7205,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/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", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-6.0.0.tgz", "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { @@ -8542,25 +7457,6 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, "nanotimer": { "version": "0.3.15", "resolved": "https://registry.npmjs.org/nanotimer/-/nanotimer-0.3.15.tgz", @@ -8715,7 +7611,7 @@ }, "node-environment-flags": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "resolved": "http://npm.zooz.co:8083/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { @@ -8959,37 +7855,6 @@ "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", @@ -9005,15 +7870,6 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", @@ -9027,7 +7883,7 @@ }, "object.getownpropertydescriptors": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { @@ -9035,15 +7891,6 @@ "es-abstract": "^1.17.0-next.1" } }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, "object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", @@ -9262,12 +8109,6 @@ "json-parse-better-errors": "^1.0.1" } }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, "parse5": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", @@ -9297,12 +8138,6 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -9427,7 +8262,7 @@ }, "picomatch": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "resolved": "http://npm.zooz.co:8083/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, @@ -9537,12 +8372,6 @@ "semver-compare": "^1.0.0" } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -9645,7 +8474,8 @@ "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true }, "qs": { "version": "6.5.2", @@ -9827,7 +8657,7 @@ }, "readdirp": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "resolved": "http://npm.zooz.co:8083/readdirp/-/readdirp-3.2.0.tgz", "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { @@ -9867,16 +8697,6 @@ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, "regexp.prototype.flags": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", @@ -9923,18 +8743,6 @@ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", @@ -10199,16 +9007,6 @@ "path-parse": "^1.0.6" } }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -10224,12 +9022,6 @@ "global-dirs": "^0.1.1" } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -10239,12 +9031,6 @@ "signal-exit": "^3.0.2" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "retry-as-promised": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", @@ -10255,7 +9041,7 @@ }, "rewire": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-5.0.0.tgz", "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { @@ -10421,6 +9207,12 @@ } } }, + "right-pad": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/right-pad/-/right-pad-1.0.1.tgz", + "integrity": "sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA=", + "dev": true + }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -10462,15 +9254,6 @@ "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10611,29 +9394,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -10805,119 +9565,6 @@ } } }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "socket.io-client": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", @@ -10993,25 +9640,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "spawn-wrap": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", @@ -11096,15 +9724,6 @@ "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -11432,27 +10051,6 @@ "escodegen": "^1.8.1" } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "stats-lite": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.2.0.tgz", @@ -11990,41 +10588,9 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, "to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { @@ -12159,18 +10725,6 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, "unique-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", @@ -12189,52 +10743,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, "unzip-response": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", @@ -12293,12 +10801,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -12307,12 +10809,6 @@ "prepend-http": "^1.0.1" } }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12623,7 +11119,7 @@ }, "yargs-unparser": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "resolved": "http://npm.zooz.co:8083/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { diff --git a/package.json b/package.json index c571cbd62..691a6b7dd 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,6 @@ "artillery": "^1.6.0-29", "bluebird": "^3.7.2", "body-parser": "^1.19.0", - "cassandra-driver": "4.3.1", - "cassandra-migration": "^2.7.0", "copy-dir": "^0.3.0", "cron": "1.7.2", "dockerode": "^2.5.8", diff --git a/src/config/databaseConfig.js b/src/config/databaseConfig.js index 0fdb48f0f..001d66312 100644 --- a/src/config/databaseConfig.js +++ b/src/config/databaseConfig.js @@ -1,23 +1,10 @@ -let cassandra = require('cassandra-driver'); const config = { type: (process.env.DATABASE_TYPE || 'SQLITE').toUpperCase(), name: process.env.DATABASE_NAME || 'predator', address: process.env.DATABASE_ADDRESS, username: process.env.DATABASE_USERNAME, password: process.env.DATABASE_PASSWORD, - cassandraReplicationFactor: process.env.CASSANDRA_REPLICATION_FACTOR || 1, - cassandraConsistency: getCassandraConsistencyByName(process.env.CASSANDRA_CONSISTENCY), - cassandraKeyspaceStrategy: process.env.CASSANDRA_KEY_SPACE_STRATEGY || 'SimpleStrategy', - cassandraLocalDataCenter: process.env.CASSANDRA_LOCAL_DATA_CENTER || 'datacenter1', sqliteStorage: process.env.SQLITE_STORAGE || 'predator' }; -function getCassandraConsistencyByName(cassandraConsistencyName) { - let consistency = cassandra.types.consistencies[cassandraConsistencyName]; - if (!consistency) { - consistency = cassandra.types.consistencies.localQuorum; - } - return consistency; -} - module.exports = config; diff --git a/src/configManager/models/database/cassandra/cassandraConnector.js b/src/configManager/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index bb59820e3..000000000 --- a/src/configManager/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,75 +0,0 @@ -const logger = require('../../../../common/logger'); -const databaseConfig = require('../../../../config/databaseConfig'); -const GET_CONFIG_VALUE = 'SELECT* FROM config WHERE key= ?'; -const GET_CONFIG = 'SELECT* FROM config'; -const INSERT_DATA = 'INSERT INTO config(key, value) values(?,?)'; -const DELETE_CONFIG = 'DELETE FROM config WHERE key=?;'; - -let client; - -module.exports = { - init, - updateConfig, - getConfig, - deleteConfig, - getConfigValue -}; -function init(cassandraClient) { - client = cassandraClient; -} - -const queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -function updateConfig(updateValues) { - let queriesArr = []; - Object.keys(updateValues).forEach(key => { - let value = updateValues[key] instanceof Object ? JSON.stringify(updateValues[key]) : updateValues[key] + ''; - queriesArr.push({ 'query': INSERT_DATA, 'params': [key, value] }); - }); - return batchUpsert(queriesArr, queryOptions); -} - -function deleteConfig(key) { - return executeQuery(DELETE_CONFIG, [key]); -} - -function getConfigValue(configValue) { - return executeQuery(GET_CONFIG_VALUE, [configValue]); -} - -function getConfig() { - return executeQuery(GET_CONFIG); -} - -async function batchUpsert(queriesArr, queryOptions) { - try { - const result = await client.batch(queriesArr, queryOptions); - logger.trace('Query result', { - queryArr: queriesArr, - queryOptions: queryOptions, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - } catch (exception) { - logger.error(`Cassandra batch failed \n ${JSON.stringify({ queriesArr, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - } -} - -async function executeQuery(query, params) { - try { - let result = await client.execute(query, params, { prepare: true }, queryOptions); - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - } catch (exception) { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - } -} \ No newline at end of file diff --git a/src/configManager/models/database/databaseConnector.js b/src/configManager/models/database/databaseConnector.js index 26b5b591b..e075db0e3 100644 --- a/src/configManager/models/database/databaseConnector.js +++ b/src/configManager/models/database/databaseConnector.js @@ -1,10 +1,8 @@ 'use strict'; -const databaseConfig = require('../../../config/databaseConfig'); -const cassandraConnector = require('./cassandra/cassandraConnector'); const sequelizeConnector = require('./sequelize/sequelizeConnector'); -const databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +const databaseConnector = sequelizeConnector; module.exports = { init, diff --git a/src/database/cassandra-handler/cassandra.js b/src/database/cassandra-handler/cassandra.js deleted file mode 100644 index ace1d11f4..000000000 --- a/src/database/cassandra-handler/cassandra.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const schedulerCassandraConnector = require('../../jobs/models/database/cassandra/cassandraConnector'); -const reportsCassandraConnector = require('../../reports/models/database/cassandra/cassandraConnector'); -const testsCassandraConnector = require('../../tests/models/database/cassandra/cassandraConnector'); -const configCassandraConnector = require('../../configManager/models/database/cassandra/cassandraConnector'); -const processorsCassandraConnector = require('../../processors/models/database/cassandra/cassandraConnector'); -const files = require('../../files/models/database/cassandra/cassandraConnector'); -const databaseConfig = require('../../config/databaseConfig'); -const cassandraMigration = require('./cassandraMigration'); -const logger = require('../../common/logger'); -const cassandra = require('cassandra-driver'); -let cassandraClient; - -module.exports.init = async () => { - cassandraClient = await createClient(); - await cassandraMigration.runMigration(); - await reportsCassandraConnector.init(cassandraClient); - await schedulerCassandraConnector.init(cassandraClient); - await testsCassandraConnector.init(cassandraClient); - await configCassandraConnector.init(cassandraClient); - await processorsCassandraConnector.init(cassandraClient); - await files.init(cassandraClient); - logger.info('cassandra client initialized'); -}; - -module.exports.ping = () => { - const query = 'SELECT * FROM system_schema.keyspaces where keyspace_name=?'; - let queryParams = [databaseConfig.name]; - return cassandraClient.execute(query, queryParams) - .then(function (results) { - if (!results.rows || results.rows.length <= 0) { - return Promise.reject(new Error('Key space wasn\'t found')); - } else { - return Promise.resolve(true); - } - }).catch(function () { - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -}; - -module.exports.closeConnection = () => { - return new Promise((resolve, reject) => { - if (cassandraClient) { - try { - cassandraClient.shutdown(); - logger.info('Cassandra client shutdown successful'); - return resolve(); - } catch (exception) { - logger.error('Failed to close Cassandra connections' + exception); - return reject(exception); - } - } else { - logger.info('Cassandra client shutdown successful'); - return resolve(); - } - }); -}; - -async function createClient() { - const authProvider = new cassandra.auth.PlainTextAuthProvider(databaseConfig.username, databaseConfig.password); - const config = { - contactPoints: String(databaseConfig.address).split(','), - keyspace: databaseConfig.name, - authProvider, - localDataCenter: databaseConfig.cassandraLocalDataCenter - }; - - let cassandraClient = new cassandra.Client(config); - return cassandraClient; -} diff --git a/src/database/cassandra-handler/cassandraMigration.js b/src/database/cassandra-handler/cassandraMigration.js deleted file mode 100644 index 3c90be75c..000000000 --- a/src/database/cassandra-handler/cassandraMigration.js +++ /dev/null @@ -1,247 +0,0 @@ -'use strict'; - -let cassandra = require('cassandra-driver'), - cassandraConfig = require('../../config/databaseConfig'), - client; -let args; -let fs = require('fs-extra'); -let cmd = require('node-cmd'); -let logger = require('../../common/logger'); -let path = require('path'); -let CREATE_KEY_SPACE_QUERY; -const CONSISTENCY_POLICY = cassandra.types.consistencies.localQuorum; -const MAX_FETCH_SIZE = 1000; -const isDevMode = process.env.DEV_MODE === 'true'; - -let initFileNameTemplate = 'cassandra_config_template.json', - initFileName = 'cassandra_config.json'; - -let cassandraHandlerLogContext = { - 'key_space_name': cassandraConfig.name, - 'initFileNameTemplate': initFileNameTemplate, - 'init_file_name': initFileName -}; - -const options = { - prepare: true, - consistency: cassandra.types.consistencies.localQuorum -}; - -module.exports.initArgs = initArgs; -module.exports.closeCassandraConnection = closeCassandraConnection; -module.exports.initCassandraConnection = initCassandraConnection; - -module.exports.runMigration = function () { - initArgs(); - - return initCassandraConnection() - .then(function () { - if (args.cassandra_keyspace_strategy === 'SimpleStrategy') { - CREATE_KEY_SPACE_QUERY = 'CREATE KEYSPACE IF NOT EXISTS ' + - args.key_space_name + - " WITH replication = {'class': 'SimpleStrategy', 'replication_factor':" + - args.replication_factor + '}'; - } else if (args.cassandra_keyspace_strategy === 'NetworkTopologyStrategy') { - CREATE_KEY_SPACE_QUERY = 'CREATE KEYSPACE IF NOT EXISTS ' + - args.key_space_name + - " WITH replication = {'class': 'NetworkTopologyStrategy', '" + args.cassandra_local_data_center + "' :" + - args.replication_factor + '}'; - } else { - throw new Error(args.cassandra_keyspace_strategy + ' is not supported cassandra keyspace strategy'); - } - return createKeySpaceIfNeeded(); - }) - .then(function () { - return closeCassandraConnection(); - }) - .then(function () { - return createConfigTemplateFile(); - }) - .then(function () { - if (!isDevMode) { - return runCassandraScripts(); - } - }) - .then(function () { - return removeConfigFile(); - }) - .then(function () { - initKeyspaceCassandraConnection(args.key_space_name); - cassandraHandlerLogContext = { - 'key_space_name': cassandraConfig.name - }; - }) - .catch(function (error) { - cassandraHandlerLogContext.initialize_cassandra_environment_error = error; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: error occurred while trying to init cassandra credentials'); - process.exit(1); - }); -}; - -module.exports.ping = function (keyspace) { - return new Promise(function (resolve, reject) { - const query = 'SELECT * FROM system_schema.keyspaces where keyspace_name=?'; - let queryParams = [keyspace]; - - client.execute(query, queryParams, options) - .then(function (results) { - if (!results.rows || results.rows.length <= 0) { - return reject(new Error('Key space doesn\'t found')); - } else { - return resolve(true); - } - }).catch(function (error) { - return reject(error); - }); - }); -}; - -function initArgs() { - args = { - key_space_name: cassandraConfig.name, - cassandra_url: cassandraConfig.address, - replication_factor: cassandraConfig.cassandraReplicationFactor, - cassandra_username: cassandraConfig.username, - cassandra_password: cassandraConfig.password, - cassandra_keyspace_strategy: cassandraConfig.cassandraKeyspaceStrategy, - cassandra_local_data_center: cassandraConfig.cassandraLocalDataCenter, - root_dir: path.join(__dirname, '../../') - }; -} - -function closeCassandraConnection() { - logger.info(cassandraHandlerLogContext, 'Cassandra handler: closing cassandra connection'); - return new Promise(function (resolve, reject) { - if (client) { - client.shutdown(function (err) { - if (err) { - cassandraHandlerLogContext.client_shutdown_err = err; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: failed to close Cassandra connection.'); - reject(err); - } else { - logger.info(cassandraHandlerLogContext, 'Cassandra handler: connection was closed successfully'); - resolve(); - } - }); - } else { - resolve(); - } - }); -} - -function runCassandraScripts() { - return new Promise(function (resolve, reject) { - let initCmd = path.join(args.root_dir, '../node_modules/.bin/cassandra-migration'); - let initConfigPath = path.join(args.root_dir, '/database/cassandra-handler', initFileName); - logger.info(cassandraHandlerLogContext, 'Cassandra handler: running migration scripts'); - - cmd.get( - initCmd + ' ' + initConfigPath, - function (err, data, stderr) { - if (err) { - cassandraHandlerLogContext.run_cassandra_scripts_err = err || stderr; - cassandraHandlerLogContext.run_cassandra_scripts_stderr = stderr; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: failed running cassandra migration scripts'); - reject(err || stderr); - } - cassandraHandlerLogContext.run_cassandra_scripts_output = data; - logger.info(cassandraHandlerLogContext, 'Cassandra handler: successfully ran cassandra migration scripts'); - delete cassandraHandlerLogContext.run_cassandra_scripts_output; - resolve(); - } - ); - }); -} - -function removeConfigFile() { - return new Promise(function (resolve, reject) { - logger.trace(cassandraHandlerLogContext, 'Cassandra handler: removiung cassandra migration config file'); - fs.remove(path.join(args.root_dir, '/database/cassandra-handler/', initFileName), function (err) { - if (err) { - cassandraHandlerLogContext.remove_config_file_err = err; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: could not remove cassandra migration config file'); - reject(err); - } - - logger.info(cassandraHandlerLogContext, 'Cassandra handler: successfully removed cassandra migration config file'); - resolve(); - }); - }); -} - -function createConfigTemplateFile() { - return new Promise(function (resolve, reject) { - let templateJsonFile = require('./' + initFileNameTemplate); - - templateJsonFile.migrationsDir = path.join(args.root_dir, '/database/cassandra-handler/init-scripts'); - templateJsonFile.cassandra.contactPoints = args.cassandra_url.split(','); - templateJsonFile.cassandra.keyspace = args.key_space_name; - templateJsonFile.auth.username = args.cassandra_username; - templateJsonFile.auth.password = args.cassandra_password; - - logger.trace(cassandraHandlerLogContext, 'Cassandra handler: set init templateJsonFile'); - - fs.writeFile(path.join(args.root_dir, 'database/cassandra-handler/', initFileName), JSON.stringify(templateJsonFile, null, 2), function (err) { - if (err) { - cassandraHandlerLogContext.remove_config_template_file_err = err; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: could not write to cassandra init file'); - reject(err); - } - logger.info(cassandraHandlerLogContext, 'Cassandra handler: successfully wrote to cassandra init file'); - resolve(); - }); - }); -} - -function initCassandraConnection() { - return new Promise(function (resolve, reject) { - client = new cassandra.Client(buildClient(undefined)); - resolve(); - }); -} - -function initKeyspaceCassandraConnection(keyspace) { - return new Promise(function (resolve, reject) { - client = new cassandra.Client(buildClient(keyspace)); - - logger.info(cassandraHandlerLogContext, 'Cassandra Handler: Successfully connected to cassandra keyspace'); - resolve(); - }); -} - -function buildClient(keyspace) { - let authProvider = new cassandra.auth.PlainTextAuthProvider(args.cassandra_username, args.cassandra_password); - let cassandraClient = { - contactPoints: args.cassandra_url.split(','), - authProvider: authProvider, - localDataCenter: args.cassandra_local_data_center - }; - - if (keyspace) { - cassandraClient['keyspace'] = keyspace; - } else { - cassandraClient['queryOptions'] = { - consistency: CONSISTENCY_POLICY, - fetchSize: MAX_FETCH_SIZE - }; - } - - logger.trace(cassandraHandlerLogContext, 'Cassandra Handler: set client configuration'); - return cassandraClient; -} - -function createKeySpaceIfNeeded() { - return new Promise(function (resolve, reject) { - client.execute(CREATE_KEY_SPACE_QUERY, null, { prepare: true }, function (err) { - if (err) { - cassandraHandlerLogContext.create_key_space_query_err = err; - cassandraHandlerLogContext.create_key_space_query_inner_err = err.innerErrors; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: could not create keyspace'); - reject(err); - } else { - logger.info(cassandraHandlerLogContext, 'Cassandra handler: CREATE_KEY_SPACE_QUERY executed successfully!'); - resolve(); - } - }); - }); -} diff --git a/src/database/cassandra-handler/cassandra_config_template.json b/src/database/cassandra-handler/cassandra_config_template.json deleted file mode 100644 index 6cb357ee5..000000000 --- a/src/database/cassandra-handler/cassandra_config_template.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "migrationsDir": "./migration-scripts", - "quiet": false, - "cassandra": { - "contactPoints": [""], - "keyspace": "", - "protocolOptions": { - "port": 9042 - }, - "socketOptions": { - "connectTimeout": 15000 - } - }, - "auth": { - "username": "", - "password": "" - } - } \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql b/src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql deleted file mode 100644 index 811c7b914..000000000 --- a/src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE tests ADD file_id uuid; diff --git a/src/database/cassandra-handler/init-scripts/11__create_files_table.cql b/src/database/cassandra-handler/init-scripts/11__create_files_table.cql deleted file mode 100644 index 4fe6dd733..000000000 --- a/src/database/cassandra-handler/init-scripts/11__create_files_table.cql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS files( -id text, -file text, -PRIMARY KEY (id)); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql b/src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql deleted file mode 100644 index c780e78d8..000000000 --- a/src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql +++ /dev/null @@ -1 +0,0 @@ -DROP MATERIALIZED VIEW IF EXISTS last_reports diff --git a/src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql b/src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql deleted file mode 100644 index 14146e707..000000000 --- a/src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE TABLE IF NOT EXISTS last_reports( -test_id uuid, -start_time_year int, -start_time_month int, -revision_id uuid, -report_id text, -test_configuration text, -last_updated_at timestamp, -start_time timestamp, -test_name text, -test_description text, -job_id text, -test_type text, -notes text, -phase text, -PRIMARY KEY ((start_time_year,start_time_month),start_time ,report_id, test_id)) -WITH CLUSTERING ORDER BY (start_time DESC); diff --git a/src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql b/src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql deleted file mode 100644 index e248f8b58..000000000 --- a/src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE jobs ADD enabled boolean; \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/14__create_processors_table.cql b/src/database/cassandra-handler/init-scripts/14__create_processors_table.cql deleted file mode 100644 index bce252e2f..000000000 --- a/src/database/cassandra-handler/init-scripts/14__create_processors_table.cql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS processors -( - id uuid, - name text, - description text, - javascript text, - created_at timestamp, - updated_at timestamp, - PRIMARY KEY (id, created_at)) - WITH CLUSTERING ORDER BY (created_at DESC); ---- -ALTER TABLE tests ADD processor_id uuid; diff --git a/src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql b/src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql deleted file mode 100644 index 5f491997c..000000000 --- a/src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE IF NOT EXISTS processors_mapping -( - name text, - id uuid, - PRIMARY KEY (name) -); diff --git a/src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql b/src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql deleted file mode 100644 index 1eac2dda1..000000000 --- a/src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE processors ADD exported_functions list; diff --git a/src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql b/src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql deleted file mode 100644 index e8e2c444f..000000000 --- a/src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS benchmarks( -test_id uuid, -data text, -PRIMARY KEY (test_id)); diff --git a/src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql b/src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql deleted file mode 100644 index 1017357c3..000000000 --- a/src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE reports_summary ADD benchmark_weights_data text; ---- -ALTER TABLE reports_summary ADD score float; ---- -ALTER TABLE last_reports ADD benchmark_weights_data text; ---- -ALTER TABLE last_reports ADD score float; - diff --git a/src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql b/src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql deleted file mode 100644 index 62843574a..000000000 --- a/src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE IF NOT EXISTS jobs( -id uuid, -test_id uuid, -environment text, -cron_expression text, -arrival_rate int, -duration int, -ramp_to int, -webhooks list, -emails list, -parallelism int, -max_virtual_users int, -notes text, -PRIMARY KEY (id) -); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/20__csv_file.cql b/src/database/cassandra-handler/init-scripts/20__csv_file.cql deleted file mode 100644 index d170cdb89..000000000 --- a/src/database/cassandra-handler/init-scripts/20__csv_file.cql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE files ADD name text; ---- -ALTER TABLE tests ADD csv_file_id uuid; diff --git a/src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql b/src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql deleted file mode 100644 index 30a43ab6b..000000000 --- a/src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS reports_summary( -test_id uuid, -revision_id uuid, -report_id text, -test_configuration text, -last_updated_at timestamp, -start_time timestamp, -test_name text, -test_description text, -job_id text, -test_type text, -notes text, -phase text, -PRIMARY KEY (test_id, report_id)); diff --git a/src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql b/src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql deleted file mode 100644 index e8e5413e5..000000000 --- a/src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS reports_stats( -runner_id text, -test_id uuid, -report_id text, -stats_id uuid, -stats_time timestamp, -phase_status text, -data text, -phase_index text, -PRIMARY KEY ((test_id, report_id), stats_time)) WITH CLUSTERING ORDER BY (stats_time ASC); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql b/src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql deleted file mode 100644 index 7f144c34b..000000000 --- a/src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS report_subscribers( -test_id uuid, -report_id text, -runner_id text, -phase_status text, -last_stats text, -PRIMARY KEY (test_id, report_id, runner_id)); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql b/src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql deleted file mode 100644 index 2e654974d..000000000 --- a/src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE MATERIALIZED VIEW last_reports_deprecated AS -SELECT * FROM reports_summary -WHERE report_id IS NOT NULL AND start_time IS NOT NULL AND test_id IS NOT NULL -PRIMARY KEY (start_time, report_id, test_id) -WITH CLUSTERING ORDER BY (start_time DESC); diff --git a/src/database/cassandra-handler/init-scripts/6__create_tests.cql b/src/database/cassandra-handler/init-scripts/6__create_tests.cql deleted file mode 100644 index fb45a58a6..000000000 --- a/src/database/cassandra-handler/init-scripts/6__create_tests.cql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS tests( -id uuid, -updated_at timestamp, -raw_data text, // The request as is that came from the client -artillery_json text, // The json that has been generated for the Artillery -revision_id uuid, -name text, -type text, -description text, -PRIMARY KEY (id, updated_at) -) -WITH compression = { 'sstable_compression' : 'LZ4Compressor' }; diff --git a/src/database/cassandra-handler/init-scripts/7__create_config_table.cql b/src/database/cassandra-handler/init-scripts/7__create_config_table.cql deleted file mode 100644 index dd311f31c..000000000 --- a/src/database/cassandra-handler/init-scripts/7__create_config_table.cql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS config( -key text, -value text, -PRIMARY KEY (key)); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/8__dsl_table.cql b/src/database/cassandra-handler/init-scripts/8__dsl_table.cql deleted file mode 100644 index 888e157c3..000000000 --- a/src/database/cassandra-handler/init-scripts/8__dsl_table.cql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS dsl( -dsl_name text, -definition_name text, -artillery_json text, // The json that has been generated for the Artillery -PRIMARY KEY (dsl_name, definition_name) -) -WITH compression = { 'sstable_compression' : 'LZ4Compressor' }; \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql b/src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql deleted file mode 100644 index 09bad07ef..000000000 --- a/src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE jobs ADD proxy_url text; ---- -ALTER TABLE jobs ADD debug text; \ No newline at end of file diff --git a/src/database/database.js b/src/database/database.js index 6ed24fc74..27e96acf8 100644 --- a/src/database/database.js +++ b/src/database/database.js @@ -1,9 +1,8 @@ 'use strict'; -let databaseConfig = require('../config/databaseConfig'); -let cassandraConnector = require('./cassandra-handler/cassandra'); let sequelizeConnector = require('./sequlize-handler/sequlize'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; + module.exports.init = () => { return databaseConnector.init(); }; @@ -14,4 +13,4 @@ module.exports.ping = () => { module.exports.closeConnection = () => { return databaseConnector.closeConnection(); -}; \ No newline at end of file +}; diff --git a/src/env.js b/src/env.js index 810fa8d65..a3eb6f8f7 100644 --- a/src/env.js +++ b/src/env.js @@ -8,9 +8,7 @@ const BY_PLATFORM_MANDATORY_VARS = { }; const SUPPORTED_PLATFORMS = Object.keys(BY_PLATFORM_MANDATORY_VARS); -const SUPPORTED_CASSANDRA_STRATEGY = ['SimpleStrategy', 'NetworkTopologyStrategy']; const BY_DATABASE_MANDATORY_VARS = { - CASSANDRA: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], MYSQL: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], POSTGRES: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], MSSQL: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], @@ -49,13 +47,6 @@ env.init = function () { log.error('Missing mandatory environment variables', missingFields); process.exit(1); } - - if (process.env.CASSANDRA_KEY_SPACE_STRATEGY && !SUPPORTED_CASSANDRA_STRATEGY.includes(process.env.CASSANDRA_KEY_SPACE_STRATEGY)) { - throw new Error('CASSANDRA_KEY_SPACE_STRATEGY not one of the supported values: ' + SUPPORTED_CASSANDRA_STRATEGY); - } - if (process.env.CASSANDRA_KEY_SPACE_STRATEGY === 'NetworkTopologyStrategy' && !process.env.CASSANDRA_LOCAL_DATA_CENTER) { - throw new Error('When using CASSANDRA_KEY_SPACE_STRATEGY: NetworkTopologyStrategy, CASSANDRA_LOCAL_DATA_CENTER is mandatory'); - } }; -module.exports = env; \ No newline at end of file +module.exports = env; diff --git a/src/files/models/database.js b/src/files/models/database.js index e70b7423b..21704ee63 100644 --- a/src/files/models/database.js +++ b/src/files/models/database.js @@ -1,7 +1,6 @@ -const cassandraConnector = require('./database/cassandra/cassandraConnector'), - sequelizeConnector = require('./database/sequelize/sequelizeConnector'), - databaseConfig = require('../../config/databaseConfig'), - databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +const sequelizeConnector = require('./database/sequelize/sequelizeConnector'); + +const databaseConnector = sequelizeConnector; module.exports = { saveFile, diff --git a/src/files/models/database/cassandra/cassandraConnector.js b/src/files/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index 11e14c1ca..000000000 --- a/src/files/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,48 +0,0 @@ -let logger = require('../../../../common/logger'); -let cassandra = require('cassandra-driver'); -let client = {}; - -const INSERT_FILE = 'INSERT INTO files(id,name,file) values(?,?,?)'; -const GET_FILE_WITH_CONTENT = 'SELECT * FROM files WHERE id = ?'; -const GET_FILE_METADATA = 'SELECT id, name FROM files WHERE id = ?'; - -module.exports = { - init, - saveFile, - getFile -}; - -let queryOptions = { - consistency: cassandra.types.consistencies.localQuorum, - prepare: true -}; - -function init(cassandraClient) { - client = cassandraClient; -} - -async function executeQuery(query, params, queryOptions) { - try { - const result = await client.execute(query, params, queryOptions); - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return result; - } catch (err){ - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, err); - throw new Error('Error occurred in communication with cassandra'); - } -} - -async function saveFile(id, fileName, fileContent) { - let params = [id, fileName, fileContent]; - const result = await executeQuery(INSERT_FILE, params, queryOptions); - return result; -} - -async function getFile(id, isIncludeContent) { - const result = await executeQuery(isIncludeContent ? GET_FILE_WITH_CONTENT : GET_FILE_METADATA, [id], queryOptions); - return result.rows[0] ? result.rows[0] : undefined; -} diff --git a/src/jobs/models/database/cassandra/cassandraConnector.js b/src/jobs/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index 28984dc46..000000000 --- a/src/jobs/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,98 +0,0 @@ -let logger = require('../../../../common/logger'); -let databaseConfig = require('../../../../config/databaseConfig'); -let client; - -const INSERT_JOB = 'INSERT INTO jobs(id, test_id, arrival_rate, cron_expression, duration, emails, environment, ramp_to, webhooks, parallelism, max_virtual_users, notes, proxy_url, debug, enabled) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; -const GET_JOBS = 'SELECT * FROM jobs'; -const DELETE_JOB = 'DELETE FROM jobs WHERE id=?'; -const GET_JOB = 'SELECT * FROM jobs WHERE id=?'; -const GET_COLUMNS = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - -let columns; - -module.exports = { - init, - insertJob, - getJobs, - getJob, - deleteJob, - updateJob -}; - -let queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -async function init(cassandraClient) { - client = cassandraClient; -} - -function deleteJob(jobId) { - return executeQuery(DELETE_JOB, [jobId]); -} - -function getJobs() { - return executeQuery(GET_JOBS, []); -} - -function getJob(jobId) { - return executeQuery(GET_JOB, [jobId]); -} - -function insertJob(jobId, jobInfo) { - let params = [jobId, jobInfo.test_id, jobInfo.arrival_rate, jobInfo.cron_expression, jobInfo.duration, jobInfo.emails, jobInfo.environment, jobInfo.ramp_to, jobInfo.webhooks, jobInfo.parallelism, jobInfo.max_virtual_users, jobInfo.notes, jobInfo.proxy_url, jobInfo.debug, jobInfo.enabled]; - return executeQuery(INSERT_JOB, params, queryOptions); -} - -async function updateJob(jobId, jobInfo) { - let params = []; - let updateQuery = 'UPDATE jobs SET '; - - if (!columns) { - columns = await getColumns(); - } - - let error; - - Object.keys(jobInfo).forEach(function (key) { - if (!(columns.indexOf(key) <= -1)) { - updateQuery += key + '=?, '; - params.push(jobInfo[key]); - } - }); - - if (error) { - return Promise.reject(error); - } - - if (params.length > 0) { - updateQuery = updateQuery.substring(0, updateQuery.length - 2); - updateQuery += ' WHERE id=? IF EXISTS'; - params.push(jobId); - return executeQuery(updateQuery, params); - } -} - -async function getColumns() { - let getColumnsResponse = await executeQuery(GET_COLUMNS, [databaseConfig.name]); - let columns = getColumnsResponse.map(row => { - return row.column_name; - }); - columns = columns.filter(column => !(columns === 'job_id' || column === 'id')); - return columns; -} - -function executeQuery(query, params, queryOptions) { - return client.execute(query, params, { prepare: true }, queryOptions).then((result) => { - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - }).catch((exception) => { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -} diff --git a/src/jobs/models/database/databaseConnector.js b/src/jobs/models/database/databaseConnector.js index 913ad8670..e27f8fa8b 100644 --- a/src/jobs/models/database/databaseConnector.js +++ b/src/jobs/models/database/databaseConnector.js @@ -1,9 +1,7 @@ 'use strict'; -let databaseConfig = require('../../../config/databaseConfig'); -let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; module.exports = { init, @@ -41,4 +39,4 @@ async function init() { function closeConnection() { return databaseConnector.closeConnection(); -} \ No newline at end of file +} diff --git a/src/processors/models/database/cassandra/cassandraConnector.js b/src/processors/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index c06f49cf1..000000000 --- a/src/processors/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,114 +0,0 @@ -let logger = require('../../../../common/logger'); -let databaseConfig = require('../../../../config/databaseConfig'); -let _ = require('lodash'); -let client; - -const JAVASCRIPT = 'javascript'; -const INSERT_PROCESSOR = 'INSERT INTO processors(id, name, description, javascript, exported_functions, created_at, updated_at) values(?,?,?,?,?,?,?)'; -const GET_ALL_PROCESSORS = 'SELECT * FROM processors'; -const GET_ALL_PROCESSORS_NO_JAVASCRIPT = 'SELECT id, name, description, created_at, updated_at, exported_functions FROM processors'; - -const GET_PROCESSOR_BY_ID = 'SELECT * FROM processors WHERE id=?'; -const DELETE_PROCESSOR = 'DELETE FROM processors WHERE id=?'; -const UPDATE_PROCESSOR = 'UPDATE processors SET name=?, description=?, javascript=?, exported_functions=?, updated_at=? WHERE id=? AND created_at=? IF EXISTS'; - -const INSERT_PROCESSOR_MAPPING = 'INSERT INTO processors_mapping(name, id) VALUES(?, ?)'; -const DELETE_PROCESSOR_MAPPING = 'DELETE FROM processors_mapping WHERE name=?'; -const GET_PROCESSOR_MAPPING = 'SELECT * FROM processors_mapping WHERE name=?'; - -module.exports = { - init, - insertProcessor, - getAllProcessors, - getProcessorById, - getProcessorByName, - deleteProcessor, - updateProcessor, - _queries: { - INSERT_PROCESSOR_MAPPING, - DELETE_PROCESSOR_MAPPING, - GET_PROCESSOR_MAPPING, - INSERT_PROCESSOR, - GET_ALL_PROCESSORS, - GET_PROCESSOR_BY_ID, - DELETE_PROCESSOR, - UPDATE_PROCESSOR - } -}; - -let queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -async function init(cassandraClient) { - client = cassandraClient; -} - -async function getAllProcessors(from, limit, exclude) { - let query = GET_ALL_PROCESSORS; - if (exclude && (exclude === JAVASCRIPT || exclude.includes(JAVASCRIPT))) { - query = GET_ALL_PROCESSORS_NO_JAVASCRIPT; - } - const resultRows = await executeQuery(query, [], queryOptions); - return _(resultRows).slice(from).take(limit).value(); -} - -async function getProcessorByName(processorName) { - const [processorMapping] = await executeQuery(GET_PROCESSOR_MAPPING, [processorName], queryOptions); - if (processorMapping) { - return getProcessorById(processorMapping.id); - } -} - -async function getProcessorById(processorId) { - const processor = await executeQuery(GET_PROCESSOR_BY_ID, [processorId], queryOptions); - return processor[0]; -} - -async function deleteProcessor(processorId) { - let params = [processorId]; - let processor = await getProcessorById(processorId); - if (processor) { - let mappingParams = [processor.name]; - return Promise.all([ - executeQuery(DELETE_PROCESSOR, params, queryOptions), - executeQuery(DELETE_PROCESSOR_MAPPING, mappingParams, queryOptions) - ]); - } -} - -async function insertProcessor(processorId, processorInfo) { - let params = [processorId, processorInfo.name, processorInfo.description, processorInfo.javascript, processorInfo.exported_functions, Date.now(), Date.now()]; - let mappingParams = [processorInfo.name, processorId]; - const [processor] = await Promise.all([ - executeQuery(INSERT_PROCESSOR, params, queryOptions), - executeQuery(INSERT_PROCESSOR_MAPPING, mappingParams, queryOptions) - ]); - return processor; -} - -async function updateProcessor(processorId, updatedProcessor) { - const { name, description, javascript, exported_functions, created_at: createdAt } = updatedProcessor; - const processor = await getProcessorById(processorId); - const params = [ name, description, javascript, exported_functions, Date.now(), processorId, createdAt.getTime() ]; - return Promise.all([ - executeQuery(UPDATE_PROCESSOR, params, queryOptions), - executeQuery(INSERT_PROCESSOR_MAPPING, [updatedProcessor.name, processorId]), - executeQuery(DELETE_PROCESSOR_MAPPING, [processor.name]) - ]); -} - -function executeQuery(query, params, queryOptions) { - return client.execute(query, params, { prepare: true }, queryOptions).then((result) => { - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - }).catch((exception) => { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -} diff --git a/src/processors/models/database/databaseConnector.js b/src/processors/models/database/databaseConnector.js index d9bccd187..68e703217 100644 --- a/src/processors/models/database/databaseConnector.js +++ b/src/processors/models/database/databaseConnector.js @@ -1,7 +1,5 @@ -let databaseConfig = require('../../../config/databaseConfig'); -let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; module.exports = { init, getAllProcessors, diff --git a/src/reports/models/database/cassandra/cassandraConnector.js b/src/reports/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index b3238d698..000000000 --- a/src/reports/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,244 +0,0 @@ -'use strict'; -const databaseConfig = require('../../../../config/databaseConfig'), - dateUtil = require('../../../utils/dateUtil'), - _ = require('lodash'), - constants = require('../../../utils/constants'); -const logger = require('../../../../common/logger'); -let client; -const isRowAppliedField = '[applied]'; -const INSERT_REPORT_SUMMARY = 'INSERT INTO reports_summary(test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; -const INSERT_LAST_REPORT_SUMMARY = 'INSERT INTO last_reports(start_time_year,start_time_month,test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; -const UPDATE_REPORT_BENCHMARK = 'UPDATE reports_summary SET score=?, benchmark_weights_data=? WHERE test_id=? AND report_id=?'; -const DELETE_REPORT_SUMMARY = 'DELETE from reports_summary WHERE test_id=? AND report_id=?'; -const GET_REPORT_SUMMARY = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; -const GET_REPORTS_SUMMARIES = 'SELECT * FROM reports_summary WHERE test_id=?'; -const GET_LAST_SUMMARIES = 'SELECT * FROM last_reports WHERE start_time_year=? AND start_time_month=? LIMIT ?'; -const INSERT_REPORT_STATS = 'INSERT INTO reports_stats(runner_id, test_id, report_id, stats_id, stats_time, phase_index, phase_status, data) values(?,?,?,?,?,?,?,?)'; -const GET_REPORT_STATS = 'SELECT * FROM reports_stats WHERE test_id=? AND report_id=?'; -const SUBSCRIBE_RUNNER = 'INSERT INTO report_subscribers(test_id, report_id, runner_id, phase_status) values(?,?,?,?)'; -const UPDATE_SUBSCRIBER_WITH_STATS = 'UPDATE report_subscribers SET phase_status=?, last_stats=? WHERE test_id=? AND report_id=? AND runner_id=?'; -const UPDATE_SUBSCRIBER = 'UPDATE report_subscribers SET phase_status=? WHERE test_id=? AND report_id=? AND runner_id=?'; -const GET_REPORT_SUBSCRIBERS = 'SELECT * FROM report_subscribers WHERE test_id=? AND report_id=?'; - -module.exports = { - init, - insertReport, - updateReport, - deleteReport, - getReport, - getReports, - getLastReports, - insertStats, - getStats, - subscribeRunner, - updateSubscriberWithStats, - updateSubscriber, - updateReportBenchmark -}; - -let queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -async function init(cassandraClient) { - client = cassandraClient; -} - -async function insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) { - let params; - const testNotes = notes || ''; - params = [testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, testNotes, lastUpdatedAt]; - const result = await executeQuery(INSERT_REPORT_SUMMARY, params, queryOptions); - if (result[0][isRowAppliedField]) { - insertLastReportAsync(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt); - } - return result; -} - -function insertLastReportAsync(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) { - let params; - const testNotes = notes || ''; - const startTimeDate = new Date(startTime); - const startTimeYear = startTimeDate.getFullYear(); - const startTimeMonth = startTimeDate.getMonth() + 1; - params = [startTimeYear, startTimeMonth, testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, testNotes, lastUpdatedAt]; - return executeQuery(INSERT_LAST_REPORT_SUMMARY, params, queryOptions) - .catch(err => logger.error(`Cassandra insertLastReportAsync failed \n ${JSON.stringify({ - INSERT_LAST_REPORT_SUMMARY, - params, - queryOptions - })}`, err)); -} - -async function updateReport(testId, reportId, reportData) { - const UPDATE_REPORT_SUMMARY = 'UPDATE reports_summary'; - const where = 'WHERE test_id=? AND report_id=?'; - const queryData = buildUpdateQuery(UPDATE_REPORT_SUMMARY, reportData, where, [testId, reportId]); - - updateLastReportAsync(testId, reportId, reportData); - return executeQuery(queryData.query, queryData.params, queryOptions); -} - -async function deleteReport(testId, reportId) { - const reportToDelete = await executeQuery(GET_REPORT_SUMMARY, [testId, reportId], queryOptions); - - const startTime = reportToDelete[0].start_time; - const startTimeDate = new Date(startTime); - const startTimeYear = startTimeDate.getFullYear(); - const startTimeMonth = startTimeDate.getMonth() + 1; - const where = 'WHERE start_time_year=? AND start_time_month=? AND start_time=? AND test_id=? AND report_id=?'; - const whereParams = [startTimeYear, startTimeMonth, startTime, testId, reportId]; - const deleteLastReport = 'DELETE from last_reports'; - - await executeQuery(`${deleteLastReport} ${where}`, whereParams, queryOptions); - await executeQuery(DELETE_REPORT_SUMMARY, [testId, reportId], queryOptions); -} - -function buildUpdateQuery(baseQuery, values, where, whereDataArray) { - const entriesValues = Object.entries(values); - const params = entriesValues.map((entry) => entry[1]).concat(whereDataArray); - const setStatement = `SET ${entriesValues.map((entry) => `${entry[0]}=?`).join(', ')}`; - const query = `${baseQuery} ${setStatement} ${where}`; - - return { - query, - params - }; -} - -async function updateReportBenchmark(testId, reportId, score, benchmarkData) { - const reportData = { - score, - benchmark_weights_data: benchmarkData - }; - updateLastReportAsync(testId, reportId, reportData); - const res = await executeQuery(UPDATE_REPORT_BENCHMARK, [score, benchmarkData, testId, reportId], { prepare: true }); - return res; -} - -async function updateLastReportAsync(testId, reportId, reportData) { - let queryData = {}; - try { - const reportToUpdate = await executeQuery(GET_REPORT_SUMMARY, [testId, reportId], queryOptions); - const startTime = reportToUpdate[0].start_time; - const startTimeDate = new Date(startTime); - const startTimeYear = startTimeDate.getFullYear(); - const startTimeMonth = startTimeDate.getMonth() + 1; - - const where = 'WHERE start_time_year=? AND start_time_month=? AND start_time=? AND test_id=? AND report_id=?'; - const whereParams = [startTimeYear, startTimeMonth, startTime, testId, reportId]; - const UPDATE_LAST_REPORT_SUMMARY = 'UPDATE last_reports'; - - queryData = buildUpdateQuery(UPDATE_LAST_REPORT_SUMMARY, reportData, where, whereParams); - - await executeQuery(queryData.query, queryData.params, queryOptions); - } catch (err) { - logger.error(`Cassandra updateLastReportAsync failed \n ${JSON.stringify({ - query: queryData.query, - params: queryData.params, - queryOptions - })}`, err); - } -} - -function getReport(testId, reportId) { - let params; - params = [testId, reportId]; - return getReportsWIthSubscribers(GET_REPORT_SUMMARY, params, queryOptions); -} - -function getReports(testId) { - let params; - params = [testId]; - return getReportsWIthSubscribers(GET_REPORTS_SUMMARIES, params, queryOptions); -} - -function getLastReports(limit) { - return getLastReportsWIthSubscribers(limit); -} - -function insertStats(runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data) { - let params; - params = [runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data]; - return executeQuery(INSERT_REPORT_STATS, params, queryOptions); -} - -function getStats(testId, reportId) { - let params; - params = [testId, reportId]; - return executeQuery(GET_REPORT_STATS, params, queryOptions); -} - -function subscribeRunner(testId, reportId, runnerId, phaseStatus) { - let params; - params = [testId, reportId, runnerId, phaseStatus]; - return executeQuery(SUBSCRIBE_RUNNER, params, queryOptions); -} - -async function updateSubscriberWithStats(testId, reportId, runnerId, phaseStatus, lastStats) { - let params; - params = [phaseStatus, lastStats, testId, reportId, runnerId]; - return executeQuery(UPDATE_SUBSCRIBER_WITH_STATS, params, queryOptions); -} - -async function updateSubscriber(testId, reportId, runnerId, phaseStatus) { - let params; - params = [phaseStatus, testId, reportId, runnerId]; - return executeQuery(UPDATE_SUBSCRIBER, params, queryOptions); -} - -function getReportSubscribers(testId, reportId) { - let params; - params = [testId, reportId]; - return executeQuery(GET_REPORT_SUBSCRIBERS, params, queryOptions); -} - -function executeQuery(query, params, queryOptions) { - return client.execute(query, params, queryOptions).then((result) => { - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - }).catch((exception) => { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -} - -async function getReportsWIthSubscribers(query, params, queryOptions) { - const reports = await executeQuery(query, params, queryOptions); - let reportsWithSubscribers = joinReportsWIthSubscribers(reports); - return reportsWithSubscribers; -} - -async function getLastReportsWIthSubscribers(limit) { - let lastReportsPromise = []; - for (let i = 0; i < constants.MAX_MONTH_OF_LAST_REPORTS; i++) { - const date = dateUtil.dateXMonthAgo(i); - lastReportsPromise.push(executeQuery(GET_LAST_SUMMARIES, [date.year, date.month, limit], queryOptions)); - } - const reportsResult = await Promise.all(lastReportsPromise); - const allReports = _(reportsResult).flatMap(value => value).value().slice(0, limit); - let reportsWIthSubscribers = joinReportsWIthSubscribers(allReports); - return reportsWIthSubscribers; -} - -async function joinReportsWIthSubscribers(reports) { - let subscribers, report; - for (let reportIndex = 0; reportIndex < reports.length; reportIndex++) { - report = reports[reportIndex]; - subscribers = await getReportSubscribers(report.test_id, report.report_id); - subscribers = subscribers.map((subscriber) => { - return { - 'runner_id': subscriber.runner_id, - 'phase_status': subscriber.phase_status, - 'last_stats': JSON.parse(subscriber.last_stats) - }; - }); - report.subscribers = subscribers; - } - return reports; -} diff --git a/src/reports/models/databaseConnector.js b/src/reports/models/databaseConnector.js index 4d20dcf2c..fe8dad43e 100644 --- a/src/reports/models/databaseConnector.js +++ b/src/reports/models/databaseConnector.js @@ -1,9 +1,7 @@ 'use strict'; -let databaseConfig = require('../../config/databaseConfig'); -let cassandraConnector = require('./database/cassandra/cassandraConnector'); let sequelizeConnector = require('./database/sequelize/sequelizeConnector'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; module.exports = { insertReport, diff --git a/src/tests/models/database.js b/src/tests/models/database.js index 1d4c85f61..ffea7beb8 100644 --- a/src/tests/models/database.js +++ b/src/tests/models/database.js @@ -1,7 +1,6 @@ -const cassandraConnector = require('./database/cassandra/cassandraConnector'), - sequelizeConnector = require('./database/sequelize/sequelizeConnector'), - databaseConfig = require('../../config/databaseConfig'), - databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +const sequelizeConnector = require('./database/sequelize/sequelizeConnector'); + +const databaseConnector = sequelizeConnector; module.exports = { insertTest, diff --git a/src/tests/models/database/cassandra/cassandraConnector.js b/src/tests/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index 9deac8520..000000000 --- a/src/tests/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,153 +0,0 @@ -let logger = require('../../../../common/logger'); -let cassandra = require('cassandra-driver'); -let client = {}; -let uuid = require('cassandra-driver').types.Uuid; -const sanitizeHelper = require('../../../helpers/sanitizeHelper'); - -const INSERT_TEST_DETAILS = 'INSERT INTO tests(id, name, description, type, updated_at, raw_data, artillery_json, revision_id, file_id, csv_file_id, processor_id) values(?,?,?,?,?,?,?,?,?,?,?)'; -const GET_TEST = 'SELECT * FROM tests WHERE id = ? ORDER BY updated_at DESC limit 1'; -const GET_TEST_REVISIONS = 'SELECT * FROM tests WHERE id = ?'; -const GET_TESTS = 'SELECT * FROM tests'; -const DELETE_TEST = 'DELETE FROM tests WHERE id=?'; -const INSERT_BENCHMARK_DATA_TEST = 'INSERT INTO benchmarks(test_id,data) values(?,?)'; -const GET_BENCHMARK_DATA_TEST = 'SELECT * FROM benchmarks WHERE test_id=?'; - -const INSERT_DSL_DEFINITION_IF_NOT_EXIST = 'INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS'; -const UPDATE_DSL_DEFINITION = 'UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'; -const DELETE_DSL_DEFINITION = 'DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'; -const GET_DSL_DEFINITION = 'SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1'; -const GET_DSL_DEFINITIONS = 'SELECT * FROM dsl WHERE dsl_name = ?'; - -module.exports = { - init, - insertTest, - getAllTestRevisions, - getTest, - getTests, - deleteTest, - insertDslDefinition, - getDslDefinition, - getDslDefinitions, - updateDslDefinition, - deleteDefinition, - insertTestBenchmark, - getTestBenchmark -}; - -let queryOptions = { - consistency: cassandra.types.consistencies.localQuorum, - prepare: true -}; - -function init(cassandraClient) { - client = cassandraClient; -} - -async function getTest(id) { - id = uuid.fromString(id); - const result = await executeQuery(GET_TEST, [id], queryOptions); - const sanitizedResult = sanitizeTestResult(result.rows)[0]; - return sanitizedResult; -} - -async function getTests() { - const result = await executeQuery(GET_TESTS, [], queryOptions); - const sanitizedResult = sanitizeTestResult(result.rows); - return sanitizedResult; -} - -async function deleteTest(testId){ - const result = await executeQuery(DELETE_TEST, [testId]); - return result; -} - -async function insertTestBenchmark(testId, benchmarkData) { - testId = uuid.fromString(testId); - const result = await executeQuery(INSERT_BENCHMARK_DATA_TEST, [testId, benchmarkData]); - return result; -} - -async function getTestBenchmark(testId) { - const result = await executeQuery(GET_BENCHMARK_DATA_TEST, [testId]); - return result.rows.length > 0 ? result.rows[0].data : undefined; -} - -async function getAllTestRevisions(id) { - id = uuid.fromString(id); - const result = await executeQuery(GET_TEST_REVISIONS, [id], queryOptions); - const sanitizedResult = await sanitizeTestResult(result.rows); - return sanitizedResult; -} - -async function insertTest(testInfo, testJson, id, revisionId, processorFileId) { - let params; - params = [id, testInfo.name, testInfo.description, testInfo.type, Date.now(), JSON.stringify(testInfo), JSON.stringify(testJson), revisionId, processorFileId, testInfo.csv_file_id, testInfo.processor_id]; - const result = await executeQuery(INSERT_TEST_DETAILS, params, queryOptions); - return result; -} - -async function getDslDefinition(dslName, definitionName) { - const params = [dslName, definitionName]; - const result = await executeQuery(GET_DSL_DEFINITION, params, queryOptions); - const sanitizedResult = sanitizeDslResult(result.rows); - return sanitizedResult[0]; -} -async function getDslDefinitions(dslName) { - const params = [dslName]; - const result = await executeQuery(GET_DSL_DEFINITIONS, params, queryOptions); - const sanitizedResult = sanitizeDslResult(result.rows); - return sanitizedResult; -} - -async function insertDslDefinition(dslName, definitionName, data) { - const params = [dslName, definitionName, JSON.stringify(data)]; - const result = await executeQuery(INSERT_DSL_DEFINITION_IF_NOT_EXIST, params, queryOptions); - return result.rows[0]['[applied]']; -} - -async function updateDslDefinition(dslName, definitionName, data) { - const params = [JSON.stringify(data), dslName, definitionName]; - const result = await executeQuery(UPDATE_DSL_DEFINITION, params, queryOptions); - return result.rows[0]['[applied]']; -} -async function deleteDefinition(dslName, definitionName) { - const params = [dslName, definitionName]; - const result = await executeQuery(DELETE_DSL_DEFINITION, params, queryOptions); - return result.rows[0]['[applied]']; -} - -async function executeQuery(query, params, queryOptions) { - try { - const result = await client.execute(query, params, queryOptions); - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return result; - } catch (err){ - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, err); - throw new Error('Error occurred in communication with cassandra'); - } -} - -function sanitizeTestResult(data) { - const result = data.map(function (row) { - const dslDataObject = sanitizeHelper.extractDslRootData(row.raw_data); - row.artillery_json = row.artillery_json ? JSON.parse(row.artillery_json) : undefined; - row.file_id = row.file_id || undefined; - row.csv_file_id = row.csv_file_id || undefined; - row.processor_id = row.processor_id || undefined; - delete row.raw_data; - return Object.assign(row, dslDataObject); - }); - return result; -} - -function sanitizeDslResult(data) { - const result = data.map(function (row) { - row.artillery_json = JSON.parse(row.artillery_json); - return row; - }); - return result; -} diff --git a/src/tests/models/dsl.js b/src/tests/models/dsl.js index 24c2c91f3..4f7dc18e4 100644 --- a/src/tests/models/dsl.js +++ b/src/tests/models/dsl.js @@ -38,7 +38,7 @@ async function createDefinition(dslName, body) { utils.addDefaultsToStep(body.request); const result = await database.insertDslDefinition(dslName, body.name, body.request); if (result){ - logger.info(body, 'Definition created successfully and saved to Cassandra'); + logger.info(body, 'Definition created successfully and saved to db'); return { name: body.name, request: body.request @@ -54,7 +54,7 @@ async function updateDefinition(dslName, definitionName, body) { utils.addDefaultsToStep(body.request); const result = await database.updateDslDefinition(dslName, definitionName, body.request); if (result){ - logger.info(body, 'Definition updated successfully and saved to Cassandra'); + logger.info(body, 'Definition updated successfully and saved to db'); return { name: body.name, request: body.request @@ -68,7 +68,7 @@ async function updateDefinition(dslName, definitionName, body) { async function deleteDefinition(dslName, definitionName, body) { const result = await database.deleteDefinition(dslName, definitionName); if (result){ - logger.info(body, 'Definition deleted successfully and saved to Cassandra'); + logger.info(body, 'Definition deleted successfully and saved to db'); } else { const error = new Error(ERROR_MESSAGES.NOT_FOUND); error.statusCode = 404; diff --git a/src/webhooks/models/database/cassandra/cassandraConnector.js b/src/webhooks/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index d5334d324..000000000 --- a/src/webhooks/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,31 +0,0 @@ -let logger = require('../../../../common/logger'); - -module.exports = { - init, - createWebhook, - getAllWebhooks, - updateWebhook, - getAllGlobalWebhooks -}; - -async function init() { - const errorMessage = 'Webhooks feature is not implemented over Cassandra'; - logger.fatal(errorMessage); - throw new Error(errorMessage); -} - -async function getAllWebhooks() { - throw new Error('Not implemented.'); -} - -async function createWebhook() { - throw new Error('Not implemented.'); -} - -async function updateWebhook() { - throw new Error('Not implemented.'); -} - -async function getAllGlobalWebhooks() { - throw new Error('Not implemented.'); -} \ No newline at end of file diff --git a/src/webhooks/models/database/databaseConnector.js b/src/webhooks/models/database/databaseConnector.js index d0381acb0..edc795246 100644 --- a/src/webhooks/models/database/databaseConnector.js +++ b/src/webhooks/models/database/databaseConnector.js @@ -1,7 +1,6 @@ -let databaseConfig = require('../../../config/databaseConfig'); -let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; + +let databaseConnector = sequelizeConnector; module.exports = { init, diff --git a/tests/configurations/cassandraConfiguration.sh b/tests/configurations/cassandraConfiguration.sh deleted file mode 100755 index 7668de4ec..000000000 --- a/tests/configurations/cassandraConfiguration.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -e - -export DATABASE_ADDRESS=$RUNNER_IP:9042 -export DATABASE_NAME=cassandra_keyspace -export DATABASE_USERNAME=root -export DATABASE_PASSWORD=password -export REPLICATION_FACTOR=1 -export DATABASE_TYPE=CASSANDRA \ No newline at end of file diff --git a/tests/configurations/dockerRun.sh b/tests/configurations/dockerRun.sh index 5b01362b3..4bef518a3 100755 --- a/tests/configurations/dockerRun.sh +++ b/tests/configurations/dockerRun.sh @@ -72,26 +72,6 @@ function postgres() { echo "$APP is ready" } -function cassandra() { - IMAGE_NAME=cassandra:3.11 - APP=cassandra - stop $APP - COMMAND="docker run \ - -d \ - --name $APP \ - -p 9042:9042 \ - $IMAGE_NAME" - echo -e "Starting $APP\n"${COMMAND/\s+/ } - $COMMAND - COMMAND_EXIT_CODE=$? - if [ ${COMMAND_EXIT_CODE} != 0 ]; then - printf "Error when executing: '${APP}'\n" - exit ${COMMAND_EXIT_CODE} - fi - waitForApp $APP "Created default superuser role 'cassandra'" - echo "$APP is ready" -} - function mailhog() { IMAGE_NAME=mailhog/mailhog APP=mailhog @@ -130,9 +110,6 @@ for option in ${@}; do postgres) postgres ;; - cassandra) - cassandra - ;; reporter) reporter ;; @@ -146,7 +123,7 @@ for option in ${@}; do stop ;; *) - echo "Usage: ./dockerRun.sh " + echo "Usage: ./dockerRun.sh " ;; esac done diff --git a/tests/integration-tests/runLocal.sh b/tests/integration-tests/runLocal.sh index cc9e1384b..9865edd15 100755 --- a/tests/integration-tests/runLocal.sh +++ b/tests/integration-tests/runLocal.sh @@ -1,9 +1,6 @@ #!/bin/bash -e -LOCAL_TEST=true DATABASE_TYPE=cassandra JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=mysql JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=sqlite JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=postgres JOB_PLATFORM=metronome ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=sqlite JOB_PLATFORM=docker ./tests/integration-tests/run.sh - - diff --git a/tests/unit-tests/cassandra-handler/cassandraHandler-test.js b/tests/unit-tests/cassandra-handler/cassandraHandler-test.js deleted file mode 100644 index a1cc9b12b..000000000 --- a/tests/unit-tests/cassandra-handler/cassandraHandler-test.js +++ /dev/null @@ -1,509 +0,0 @@ -// 'use strict'; -// -// let sinon = require('sinon'); -// let rewire = require('rewire'); -// let chai = require('chai'); -// let expect = chai.expect; -// let chaiSinon = require('chai-sinon'); -// chai.use(chaiSinon); -// let logger = require('../../../src/helpers/logger'); -// let cassandra = require('cassandra-driver'); -// let cassandraConfig = require('../../../src/config/databaseConfig'); -// let cassandraHandler = rewire('../../../src/cassandra-handler/cassandraHandler'); -// -// describe('cassandra handler tests', function () { -// let sandbox; -// cassandraConfig.address = 'localhost:9042'; -// cassandraConfig.username = 'username'; -// cassandraConfig.password = 'password'; -// cassandraConfig.name = 'predator'; -// -// before(function () { -// sandbox = sinon.sandbox.create(); -// }); -// -// afterEach(function () { -// sandbox.restore(); -// }); -// -// describe('cassandra ping tests', function () { -// let clientExecuteStub; -// -// beforeEach(function () { -// sandbox.reset(); -// let client = { -// execute: sinon.stub() -// }; -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// clientExecuteStub = client.execute; -// }); -// -// it('ping is ok, cassandra is up', function (done) { -// clientExecuteStub.resolves({rows: [{}]}); -// -// cassandraHandler.initArgs(); -// cassandraHandler.initCassandraConnection() -// .then(function () { -// return cassandraHandler.ping('keyspace'); -// }).then(function (result) { -// expect(result).to.be.true; -// done(); -// }).catch(function (error) { -// done(error); -// }); -// }); -// -// it('ping rejects as no schema, cassandra is down', function (done) { -// clientExecuteStub.resolves({rows: []}); -// -// cassandraHandler.initArgs(); -// cassandraHandler.initCassandraConnection() -// .then(function () { -// return cassandraHandler.ping('keyspace'); -// }).then(function () { -// done('Expecting test to fail'); -// }).catch(function (error) { -// expect(error.message).to.eql('Key space doesn\'t found'); -// done(); -// }); -// }); -// -// it('cassandra rejects with error, cassandra is down', function (done) { -// clientExecuteStub.rejects({message: 'failure'}); -// -// cassandraHandler.initArgs(); -// cassandraHandler.initCassandraConnection() -// .then(function () { -// return cassandraHandler.ping('keyspace'); -// }).then(function () { -// done('Expecting test to fail'); -// }).catch(function (error) { -// expect(error.message).to.eql('failure'); -// done(); -// }); -// }); -// }); -// -// describe('initializeCassandraEnvironment tests', function () { -// beforeEach(function () { -// cassandraConfig.address = 'localhost:9042'; -// cassandraConfig.username = 'cassandra'; -// cassandraConfig.password = 'cassandra'; -// let cassandraHandlerLogContext = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': cassandraConfig.name, -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json' -// }; -// cassandraHandler.__set__('cassandraHandlerLogContext', cassandraHandlerLogContext); -// }); -// -// afterEach(function () { -// sandbox.restore(); -// }); -// -// describe('createKeySpaceIfNeeded rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = { -// innerErrors: 'client execute error' -// }; -// clientExecuteStub.yields(error); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(null); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'create_key_space_query_err': { -// 'innerErrors': 'client execute error' -// }, -// 'create_key_space_query_inner_err': 'client execute error', -// 'initialize_cassandra_environment_error': { -// 'innerErrors': 'client execute error' -// } -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.not.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.not.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.not.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: could not create keyspace')).to.be.true; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: error occurred while trying to init cassandra credentials')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }).catch(function (error) { -// done(error); -// }); -// }); -// }); -// describe('closeCassandraConnection rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = { -// innerErrors: 'client execute error' -// }; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(error); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(null); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'client_shutdown_err': { -// 'innerErrors': 'client execute error' -// }, -// 'initialize_cassandra_environment_error': { -// 'innerErrors': 'client execute error' -// } -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.not.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.not.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: failed to close Cassandra connection.')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// describe('createConfigTemplateFile rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = 'error_message'; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(error); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'remove_config_template_file_err': 'error_message', -// 'initialize_cassandra_environment_error': 'error_message' -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.not.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: could not write to cassandra init file')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// describe('runCassandraScripts rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = 'error_message'; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(undefined); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(error, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'run_cassandra_scripts_err': 'error_message', -// 'run_cassandra_scripts_stderr': null, -// 'initialize_cassandra_environment_error': 'error_message' -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: failed running cassandra migration scripts')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// describe('removeConfigFile rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = 'error_message'; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(undefined); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(error); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'remove_config_file_err': 'error_message', -// 'initialize_cassandra_environment_error': 'error_message' -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.been.calledOnce; -// expect(fsRemoveStub).to.have.been.calledOnce; -// expect(cmdStub).to.have.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: could not remove cassandra migration configManager file')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// }); -// }); diff --git a/tests/unit-tests/configManager/cassandra/cassandra-test.js b/tests/unit-tests/configManager/cassandra/cassandra-test.js deleted file mode 100644 index 94b2fe3a1..000000000 --- a/tests/unit-tests/configManager/cassandra/cassandra-test.js +++ /dev/null @@ -1,128 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let should = require('should'); -let cassandraClient = rewire('../../../../src/configManager/models/database/cassandra/cassandraConnector'); - -describe('Cassandra client tests', function() { - let sandbox; - let clientBatchStub; - let clientExecuteStub; - let revert; - - before(() => { - sandbox = sinon.sandbox.create(); - clientBatchStub = sandbox.stub(driver.Client.prototype, 'batch'); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { batch: clientBatchStub, execute: clientExecuteStub }); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('Upsert new config record', () => { - it('should succeed simple update', async () => { - clientBatchStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO config(key, value) values(?,?)'; - await cassandraClient.updateConfig({ key: 'test_key' }); - - clientBatchStub.getCall(0).args[0][0].query.should.eql(query); - clientBatchStub.getCall(0).args[0][0].params[0].should.eql('key'); - clientBatchStub.getCall(0).args[0][0].params[1].should.eql('test_key'); - }); - }); - - describe('Upsert new config record object as value', () => { - it('should succeed object value update', async () => { - clientBatchStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO config(key, value) values(?,?)'; - let objectToSave = { test_json: 'json_value' }; - await cassandraClient.updateConfig({ key: objectToSave }); - - clientBatchStub.getCall(0).args[0][0].query.should.eql(query); - clientBatchStub.getCall(0).args[0][0].params[0].should.eql('key'); - clientBatchStub.getCall(0).args[0][0].params[1].should.eql(JSON.stringify(objectToSave)); - }); - }); - - describe('Upsert new config multiple records object and strings as value', () => { - it('should succeed object value update', async () => { - clientBatchStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO config(key, value) values(?,?)'; - let objectToSave = { object_key: 'test_key' }; - await cassandraClient.updateConfig({ stringValue: 'test_string', objectValue: objectToSave }); - - clientBatchStub.getCall(0).args[0][0].query.should.eql(query); - clientBatchStub.getCall(0).args[0][0].params[0].should.eql('stringValue'); - clientBatchStub.getCall(0).args[0][0].params[1].should.eql('test_string'); - clientBatchStub.getCall(0).args[0][1].params[0].should.eql('objectValue'); - clientBatchStub.getCall(0).args[0][1].params[1].should.eql(JSON.stringify(objectToSave)); - }); - }); - - describe('get all config', () => { - it('should succeed get config', async () => { - clientExecuteStub.resolves(new Promise((resolve, reject) => { - resolve({}); - })); - let query = 'SELECT* FROM config'; - await cassandraClient.getConfig(); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - }); - }); - describe('handle cassandra delete ', () => { - it('should succeed delete', async () => { - const query = 'DELETE FROM config WHERE key=?;'; - clientExecuteStub.resolves([]); - await cassandraClient.deleteConfig('delete_key'); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('delete_key'); - }); - }); - - describe('get config by value multple ', () => { - it('should succeed get config', async () => { - clientExecuteStub.resolves(new Promise((resolve, reject) => { - resolve({}); - })); - let query = 'SELECT* FROM config WHERE key= ?'; - await cassandraClient.getConfigValue('value_test'); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('value_test'); - }); - }); - - describe('handle cassandra execute error ', () => { - it('should reject request with error', async () => { - clientExecuteStub.throws(); - let errorText = 'Error occurred in communication with cassandra'; - - cassandraClient.getConfigValue('value_test').then(() => { - throw new Error('Expected to catch error!'); - }, (err) => { - should(err.message).eql(errorText); - }); - }); - }); - describe('handle cassandra batch error ', () => { - it('should reject request with error', async () => { - clientBatchStub.throws(); - let errorText = 'Error occurred in communication with cassandra'; - - cassandraClient.updateConfig({}).then(() => { - throw new Error('Expected to catch error!'); - }, (err) => { - should(err.message).eql(errorText); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/unit-tests/configManager/configHandler-test.js b/tests/unit-tests/configManager/configHandler-test.js index e0bd8b52c..d8ddd08c3 100644 --- a/tests/unit-tests/configManager/configHandler-test.js +++ b/tests/unit-tests/configManager/configHandler-test.js @@ -5,6 +5,7 @@ process.env.JOB_PLATFORM = 'DOCKER'; const should = require('should'); const rewire = require('rewire'); const sinon = require('sinon'); + const databaseConnector = require('../../../src/configManager/models/database/databaseConnector'); const configConstants = require('../../../src/common/consts').CONFIG; @@ -118,15 +119,15 @@ const resultAfterConvert = { describe('Manager config', function () { let sandbox; - let cassandraGetStub; - let cassandraGetValueStub; - let cassandraUpdateStub; + let databaseConnectorGetStub; + let databaseConnectorGetValueStub; + let databaseConnectorUpdateStub; before(() => { sandbox = sinon.sandbox.create(); - cassandraGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); - cassandraGetValueStub = sandbox.stub(databaseConnector, 'getConfigValue'); - cassandraUpdateStub = sandbox.stub(databaseConnector, 'updateConfig'); + databaseConnectorGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); + databaseConnectorGetValueStub = sandbox.stub(databaseConnector, 'getConfigValue'); + databaseConnectorUpdateStub = sandbox.stub(databaseConnector, 'updateConfig'); manager = rewire('../../../src/configManager/models/configHandler'); }); @@ -140,7 +141,7 @@ describe('Manager config', function () { describe('get default config', function () { it('get default config success', async () => { - cassandraGetStub.resolves([]); + databaseConnectorGetStub.resolves([]); let result = await manager.getConfig(); @@ -152,7 +153,7 @@ describe('Manager config', function () { describe('get config from default and DB', function () { it('get config success', async () => { - cassandraGetStub.resolves({ 'runner_cpu': 2 }); + databaseConnectorGetStub.resolves({ 'runner_cpu': 2 }); let result = await manager.getConfig(); should(Object.keys(result).length).eql(Object.keys(configConstants).length); Object.keys(result).forEach(key => { @@ -166,7 +167,7 @@ describe('Manager config', function () { describe('get config with corrupted data from DB', function () { it('get config success', async () => { - cassandraGetStub.resolves({ 'key_not_valid': 2 }); + databaseConnectorGetStub.resolves({ 'key_not_valid': 2 }); let result = await manager.getConfig(); const resultEscapedUndefined = escapeUndefinedValues(result); should(resultEscapedUndefined).eql(defaultConfig); @@ -175,7 +176,7 @@ describe('Manager config', function () { describe('get config and parse types, types are valid', function () { it('get config success', async () => { - cassandraGetStub.resolves(configResponseParseObject); + databaseConnectorGetStub.resolves(configResponseParseObject); let result = await manager.getConfig(); @@ -186,7 +187,7 @@ describe('Manager config', function () { describe('get config value from env variables', function () { it('get config value success', async () => { - cassandraGetValueStub.resolves(undefined); + databaseConnectorGetValueStub.resolves(undefined); let result = await manager.getConfigValue('runner_cpu'); should(result).eql(1); @@ -195,7 +196,7 @@ describe('Manager config', function () { describe('update config ', function () { it('update config success', async () => { - cassandraUpdateStub.resolves([]); + databaseConnectorUpdateStub.resolves([]); let result = await manager.updateConfig({ runner_cpu: 'test_runner_cpu' }); should(result).eql([]); diff --git a/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js b/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js index 5b6e164be..fe3c21c0e 100644 --- a/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js +++ b/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js @@ -11,11 +11,11 @@ const configConstants = require('../../../src/common/consts').CONFIG; describe('Manager config with env variables', function () { let sandbox; let manager; - let cassandraGetStub; + let databaseConnectorGetStub; before(() => { sandbox = sinon.sandbox.create(); - cassandraGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); + databaseConnectorGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); process.env.SMTP_FROM = 'smtp_from_test'; process.env.SMTP_PORT = 'smtp_port_test'; @@ -50,7 +50,7 @@ describe('Manager config with env variables', function () { sandbox.restore(); }); it('get config for from env varibles in the right types (json,int,float,string)', async () => { - cassandraGetStub.resolves([]); + databaseConnectorGetStub.resolves([]); let result = await manager.getConfig(); should(Object.keys(result).length).eql(Object.keys(configConstants).length); should(result.grafana_url).eql('url_test'); diff --git a/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js b/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js index 4e9eba8ab..8bc604344 100644 --- a/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js @@ -4,7 +4,7 @@ const sinon = require('sinon'), databaseConfig = require('../../../../src/config/databaseConfig'), sequelizeConnector = require('../../../../src/configManager/models/database/sequelize/sequelizeConnector'); -describe('Cassandra client tests', function () { +describe('Sequelize client tests', function () { let sandbox, sequelizeModelStub, sequelizeUpsertStub, @@ -123,4 +123,4 @@ describe('Cassandra client tests', function () { should(sequelizeGeValueetStub.args[0][0].where.key).eql('key_value'); }); }); -}); \ No newline at end of file +}); diff --git a/tests/unit-tests/env-test.js b/tests/unit-tests/env-test.js index be029e400..6c9d4934e 100644 --- a/tests/unit-tests/env-test.js +++ b/tests/unit-tests/env-test.js @@ -60,7 +60,7 @@ describe.skip('Env Suite', function () { it('Should Failed - missing all mandatory env', () => { should(logErrorStub.called).eql(true); - should(logErrorStub.args[0][0]).eql('DATABASE_TYPE should be one of: CASSANDRA,MYSQL,POSTGRES,MSSQL,SQLITE'); + should(logErrorStub.args[0][0]).eql('DATABASE_TYPE should be one of: MYSQL,POSTGRES,MSSQL,SQLITE'); }); MANDATORY_VARS.forEach(function (varb) { diff --git a/tests/unit-tests/files/models/cassandraConnector-test.js b/tests/unit-tests/files/models/cassandraConnector-test.js deleted file mode 100644 index 06f476db2..000000000 --- a/tests/unit-tests/files/models/cassandraConnector-test.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let should = require('should'); -let cassandraClient = require('../../../../src/files/models/database/cassandra/cassandraConnector'); -let uuid = require('uuid'); - -describe('Cassandra client tests', function () { - let sandbox; - let clientExecuteStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(); - cassandraClient.init({ execute: clientExecuteStub }); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - }); - - describe('Create and get files', function () { - it('should succeed simple insert of file', async () => { - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - - let query = 'INSERT INTO files(id,name,file) values(?,?,?)'; - await cassandraClient.saveFile(id, 'some_file.txt', 'contentcontentcontent'); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - clientExecuteStub.getCall(0).args[1][1].should.eql('some_file.txt'); - clientExecuteStub.getCall(0).args[1][2].should.eql('contentcontentcontent'); - }); - it('should succeed simple get of file', async () => { - clientExecuteStub.resolves({ rows: [ { name: 'file.txt', file: 'abcdef' }] }); - let id = uuid.v4(); - - let query = 'SELECT id, name FROM files WHERE id = ?'; - let file = await cassandraClient.getFile(id); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - - file.name.should.eql('file.txt'); - file.file.should.eql('abcdef'); - }); - - it('should succeed simple get of file with content', async () => { - clientExecuteStub.resolves({ rows: [ { name: 'file.txt', file: 'abcdef' }] }); - let id = uuid.v4(); - - let query = 'SELECT * FROM files WHERE id = ?'; - let file = await cassandraClient.getFile(id, true); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - - file.name.should.eql('file.txt'); - file.file.should.eql('abcdef'); - }); - - it('should return undefined when file not found', async () => { - clientExecuteStub.resolves({ rows: [] }); - let id = uuid.v4(); - - let query = 'SELECT id, name FROM files WHERE id = ?'; - let file = await cassandraClient.getFile(id); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - - should(file).eql(undefined); - }); - }); -}); diff --git a/tests/unit-tests/files/models/database-test.js b/tests/unit-tests/files/models/database-test.js deleted file mode 100644 index d32555e36..000000000 --- a/tests/unit-tests/files/models/database-test.js +++ /dev/null @@ -1,61 +0,0 @@ - -const should = require('should'), - sinon = require('sinon'), - cassandra = require('../../../../src/files/models/database/cassandra/cassandraConnector'), - sequelizeConnector = require('../../../../src/files/models/database/sequelize/sequelizeConnector'), - rewire = require('rewire'), - databaseConfig = require('../../../../src/config/databaseConfig'); -let database = require('../../../../src/files/models/database'); -const functions = [ - { - functionName: 'saveFile', - args: ['id', 'name', 'file'] - }, - { - functionName: 'getFile', - args: ['id', true] - }, -]; - -describe('Testing database', function () { - let sandbox; - before(function () { - process.env.DATABASE_TYPE = 'CASSANDRA'; - sandbox = sinon.sandbox.create(); - functions.forEach(function (func) { - sandbox.stub(cassandra, func.functionName); - sandbox.stub(sequelizeConnector, func.functionName); - }); - }); - beforeEach(function () { - sandbox.resetHistory(); - }); - after(function () { - sandbox.restore(); - }); - - describe('when database type is cassandra - should applied functions on cassandra client', function () { - before(async function () { - databaseConfig.type = 'cassandra'; - database = rewire('../../../../src/files/models/database'); - }); - functions.forEach(function (func) { - it(`checking func: ${func.functionName}`, async function () { - await database[func.functionName](...func.args); - should(cassandra[func.functionName].args).eql([func.args]); - }); - }); - }); - describe('when database type is not cassandra - should applied functions on sequlize client', function () { - before(async function () { - databaseConfig.type = 'not-cassandra'; - database = rewire('../../../../src/files/models/database'); - }); - functions.forEach(function (func) { - it(`checking func: ${func.functionName}`, async function () { - await database[func.functionName](...func.args); - should(sequelizeConnector[func.functionName].args).eql([func.args]); - }); - }); - }); -}); diff --git a/tests/unit-tests/jobs/cassandra/cassandra-test.js b/tests/unit-tests/jobs/cassandra/cassandra-test.js deleted file mode 100644 index 3f07ce2e2..000000000 --- a/tests/unit-tests/jobs/cassandra/cassandra-test.js +++ /dev/null @@ -1,230 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let should = require('should'); -let cassandraClient = rewire('../../../../src/jobs/models/database/cassandra/cassandraConnector'); - -let uuid = require('uuid'); - -describe('Cassandra client tests', function() { - let sandbox; - let clientExecuteStub; - let revert; - let loggerErrorStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('Init and shutdown tests', function(){ - it('it should initialize cassandra client successfully', (done) => { - try { - cassandraClient.init({ execute: clientExecuteStub }); - } catch (e) { - e.should.be.equal(undefined); - e.should.not.be.instanceOf(Error); - } - done(); - }); - }); - - describe('Insert new test tests', function(){ - it('should succeed simple insert', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let query = 'INSERT INTO jobs(id, test_id, arrival_rate, cron_expression, duration, emails, environment, ramp_to, webhooks, parallelism, max_virtual_users, notes, proxy_url, debug, enabled) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; - return cassandraClient.insertJob(id, { test_id: testId, arrival_rate: 1, duration: 1, cron_expression: '* * * *', emails: {}, environment: 'test', ramp_to: '1', webhooks: 1, parallelism: 3, max_virtual_users: 500, notes: 'hello' }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - clientExecuteStub.getCall(0).args[1][1].should.eql(testId); - }); - }); - - it('should log error for failing inserting new test', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertJob(uuid.v4(), { test_id: uuid.v4(), arrival_rate: 1, duration: 1, cron_expression: '* * * *', emails: {}, environment: 'test', ramp_to: '1', webhooks: 1, notes: 'hello' }) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get jobs', function(){ - it('should get multiple jobs', function(){ - let cassandraResponse = { rows: [{ id: 'id', test_id: 'test_id', arrival_rate: 1, duration: 1, cron_expression: null, emails: null, webhooks: null, ramp_to: '1' }] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM jobs'; - return cassandraClient.getJobs() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM jobs'; - return cassandraClient.getJobs() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get job', function(){ - it('should get single job', function(){ - clientExecuteStub.resolves({ rows: [{ id: 'id', test_id: 'test_id', arrival_rate: 1, duration: 1, cron_expression: null, emails: null, webhooks: null, ramp_to: '1' }] }); - let jobId = uuid.v4(); - let query = 'SELECT * FROM jobs WHERE id=?'; - return cassandraClient.getJob(jobId) - .then(function(result){ - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(jobId); - }); - }); - }); - - describe('Delete job', function(){ - it('should delete single job', function(){ - clientExecuteStub.resolves({ rows: [] }); - let jobId = uuid.v4(); - let query = 'DELETE FROM jobs WHERE id=?'; - return cassandraClient.deleteJob(jobId) - .then(function(result){ - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(jobId); - result.should.eql([]); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - let jobId = uuid.v4(); - let query = 'DELETE FROM jobs WHERE id=?'; - return cassandraClient.deleteJob(jobId) - .then(function(){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Update job', function(){ - it('should succeed update of one parameter', function(){ - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - cassandraClient.__set__('databaseConfig', { name: 'keyspace' }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let updateQuery = 'UPDATE jobs SET test_id=? WHERE id=? IF EXISTS'; - let columnQuery = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(id, { test_id: testId }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(columnQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - clientExecuteStub.getCall(1).args[0].should.eql(updateQuery); - clientExecuteStub.getCall(1).args[1][1].should.eql(id); - clientExecuteStub.getCall(1).args[1][0].should.eql(testId); - }); - }); - - it('should succeed update more than one parameter', function(){ - cassandraClient.__set__('columns', undefined); - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }, { column_name: 'duration' }] }); - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let query = 'UPDATE jobs SET test_id=?, duration=? WHERE id=? IF EXISTS'; - let columnQuery = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(id, { test_id: testId, duration: 4 }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(columnQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - clientExecuteStub.getCall(1).args[0].should.eql(query); - clientExecuteStub.getCall(1).args[1][2].should.eql(id); - clientExecuteStub.getCall(1).args[1][1].should.eql(4); - clientExecuteStub.getCall(1).args[1][0].should.eql(testId); - }); - }); - - it('should ignore none existing parameter in the update', function(){ - cassandraClient.__set__('columns', undefined); - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let query = 'UPDATE jobs SET test_id=? WHERE id=? IF EXISTS'; - let columnQuery = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(id, { test_id: testId, duration: 4 }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(columnQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - clientExecuteStub.getCall(1).args[0].should.eql(query); - clientExecuteStub.getCall(1).args[1][1].should.eql(id); - clientExecuteStub.getCall(1).args[1][0].should.eql(testId); - }); - }); - - it('should log error for failing updating new test', function(){ - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - clientExecuteStub.rejects(); - return cassandraClient.updateJob(uuid.v4(), { test_id: uuid.v4() }) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - - ['id', 'job_id'].forEach(function(idName){ - it('should reject an error for trying to update ' + idName, function(){ - cassandraClient.__set__('columns', undefined); - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - cassandraClient.__set__('databaseConfig', { name: 'keyspace' }); - - let query = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(uuid.v4(), { [idName]: 'something' }) - .catch(function(err){ - err.statusCode.should.eql(400); - err.message.should.eql('Job id can not be updated'); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/unit-tests/processors/cassandra/cassandra-test.js b/tests/unit-tests/processors/cassandra/cassandra-test.js deleted file mode 100644 index 1b39a2881..000000000 --- a/tests/unit-tests/processors/cassandra/cassandra-test.js +++ /dev/null @@ -1,242 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let should = require('should'); -let cassandraClient = rewire('../../../../src/processors/models/database/cassandra/cassandraConnector'); - -let uuid = require('uuid'); - -describe('Cassandra processors tests', function() { - let sandbox; - let clientExecuteStub; - let revert; - let loggerErrorStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('init', function() { - it('should assign the cassandra client successfully', function () { - const prevClient = cassandraClient.__get__('client'); - const newClient = { clientId: 'fake-client' }; - cassandraClient.init(newClient); - const updatedClient = cassandraClient.__get__('client'); - updatedClient.should.equal(newClient); - cassandraClient.__set__('client', prevClient); - }); - }); - - describe('Insert new processor', function(){ - it('should succeed simple insert', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - - let query = cassandraClient._queries.INSERT_PROCESSOR; - return cassandraClient.insertProcessor(id, { name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'' }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - }); - }); - - it('should log error for failing inserting new processor', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertProcessor('id', { name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'' }) - .catch(function(){ - loggerErrorStub.callCount.should.eql(2); - }); - }); - }); - - describe('Get processors', function(){ - it('should get multiple processors', function(){ - let cassandraResponse = { rows: [{ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'', created_at: Date.now(), updated_at: Date.now() }] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM processors'; - return cassandraClient.getAllProcessors() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get multiple processors while excluding javascript', function(){ - let cassandraResponse = { rows: [{ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'', created_at: Date.now(), updated_at: Date.now() }] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT id, name, description, created_at, updated_at, exported_functions FROM processors'; - return cassandraClient.getAllProcessors(undefined, undefined, 'javascript') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = cassandraClient._queries.GET_ALL_PROCESSORS; - return cassandraClient.getAllProcessors() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get processor', function() { - describe('getProcessorById', function() { - it('should get a single processor', function(){ - clientExecuteStub.resolves({ rows: [{ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'', created_at: Date.now(), updated_at: Date.now() }] }); - let proccesorId = uuid.v4(); - let query = cassandraClient._queries.GET_PROCESSOR_BY_ID; - return cassandraClient.getProcessorById(proccesorId) - .then(function(result){ - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(proccesorId); - should(result).containDeep({ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'' }); - }); - }); - }); - - describe('updateProcessor', function() { - it('should update the execute the update query sucessfully', async function() { - const getProcessorQuery = cassandraClient._queries.GET_PROCESSOR_BY_ID; - const updateProcessorQuery = cassandraClient._queries.UPDATE_PROCESSOR; - const deleteProcessorMapping = cassandraClient._queries.DELETE_PROCESSOR_MAPPING; - const insertProcessorMapping = cassandraClient._queries.INSERT_PROCESSOR_MAPPING; - const processorId = uuid.v4(); - const processor = { - id: processorId, - name: 'updated processor name', - description: 'some processor', - javascript: 'module.exports.mick = \'ey\'', - created_at: Date.now() - }; - const processorMapping = { - name: processor.name, - id: processorId - }; - clientExecuteStub.withArgs(getProcessorQuery).resolves({ rows: [processor] }); - clientExecuteStub.withArgs(updateProcessorQuery).resolves({ rows: [processor] }); - clientExecuteStub.withArgs(insertProcessorMapping).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(deleteProcessorMapping).resolves({ rows: [] }); - cassandraClient.updateProcessor(processorId, processor).then(result => { - clientExecuteStub.getCall(0).args[0].should.eql(getProcessorQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql(processorId); - clientExecuteStub.getCall(1).args[0].should.eql(updateProcessorQuery); - clientExecuteStub.getCall(1).args[1][0].should.eql(processor.name); - clientExecuteStub.getCall(2).args[0].should.eql(insertProcessorMapping); - clientExecuteStub.getCall(2).args[1][0].should.eql(processor.name); - clientExecuteStub.getCall(2).args[1][1].should.eql(processor.id); - clientExecuteStub.getCall(3).args[0].should.eql(deleteProcessorMapping); - clientExecuteStub.getCall(3).args[1][0].should.eql(processor.name); - should(result).containDeep(processor); - }); - }); - }); - - describe('getProcessorByName', function() { - it('should get a single processor', function() { - let processorId = uuid.v4(); - let processorName = 'Generate Random Kitty Name'; - const processor = { - id: processorId, - name: processorName, - description: 'some processor', - javascript: 'module.exports.mick = \'ey\'', - created_at: Date.now(), - updated_at: Date.now() - }; - const processorMapping = { - name: processorName, - id: processorId - }; - let query = cassandraClient._queries.GET_PROCESSOR_BY_ID; - let mappingQuery = cassandraClient._queries.GET_PROCESSOR_MAPPING; - clientExecuteStub.withArgs(mappingQuery).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(query).resolves({ rows: [processor] }); - return cassandraClient.getProcessorByName(processorName) - .then(function(result) { - clientExecuteStub.getCall(0).args[0].should.eql(mappingQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql(processorName); - clientExecuteStub.getCall(1).args[0].should.eql(query); - clientExecuteStub.getCall(1).args[1][0].should.eql(processorId); - should(result).containDeep(processor); - }); - }); - }); - }); - - describe('Delete processor', function(){ - it('should delete single processor', function(){ - const processorId = uuid.v4(); - const processorMapping = { - name: 'mick', - id: processorId - }; - const getProcessorQuery = cassandraClient._queries.GET_PROCESSOR_BY_ID; - const deleteProcessorQuery = cassandraClient._queries.DELETE_PROCESSOR; - const deleteMappingQuery = cassandraClient._queries.DELETE_PROCESSOR_MAPPING; - clientExecuteStub.withArgs(getProcessorQuery).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(deleteProcessorQuery).resolves({ rows: [] }); - clientExecuteStub.withArgs(deleteMappingQuery).resolves({ rows: [] }); - - return cassandraClient.deleteProcessor(processorId) - .then(function(result){ - clientExecuteStub.callCount.should.eql(3); - clientExecuteStub.getCall(0).args[0].should.eql(getProcessorQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql(processorMapping.id); - clientExecuteStub.getCall(1).args[0].should.eql(deleteProcessorQuery); - clientExecuteStub.getCall(1).args[1][0].should.eql(processorId); - clientExecuteStub.getCall(2).args[0].should.eql(deleteMappingQuery); - clientExecuteStub.getCall(2).args[1][0].should.eql(processorMapping.name); - result.should.eql([[], []]); - }); - }); - - it('should get failure from cassandra', function(){ - let processorId = uuid.v4(); - const processorMapping = { - id: processorId, - name: 'mick' - }; - let getProcessorMapping = cassandraClient._queries.GET_PROCESSOR_BY_ID; - let deleteProcessorQuery = cassandraClient._queries.DELETE_PROCESSOR; - clientExecuteStub.withArgs(getProcessorMapping).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(deleteProcessorQuery).rejects(new Error('error')); - return cassandraClient.deleteProcessor(processorId) - .then(function(){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(getProcessorMapping); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); -}); diff --git a/tests/unit-tests/reporter/cassandra/cassandra-test.js b/tests/unit-tests/reporter/cassandra/cassandra-test.js deleted file mode 100644 index aa91fdccf..000000000 --- a/tests/unit-tests/reporter/cassandra/cassandra-test.js +++ /dev/null @@ -1,385 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let cassandraClient = rewire('../../../../src/reports/models/database/cassandra/cassandraConnector'); - -let uuid = require('uuid'); - -const REPORT = { - 'test_id': 'test id', - 'revision_id': 'revision_id', - 'report_id': 'report_id', - 'test_name': 'test name', - 'report_url': 'http://www.zooz.com', - 'last_stats': JSON.stringify({ - 'timestamp': '2018-05-28T15:40:10.044Z', - 'scenariosCreated': 289448, - 'scenariosCompleted': 289447, - 'requestsCompleted': 694611, - 'latency': { - 'min': 6.3, - 'max': 3822.8, - 'median': 58.8, - 'p95': 115.5, - 'p99': 189.4 - }, - 'rps': { - 'count': 694611, - 'mean': 178.61 - }, - 'scenarioDuration': { - 'min': 80.4, - 'max': 5251.7, - 'median': 146.8, - 'p95': 244.4, - 'p99': 366.6 - }, - 'scenarioCounts': { - 'Create token and get token': 173732, - 'Create token, create customer and assign token to customer': 115716 - }, - 'errors': { EAI_AGAIN: 112, NOTREACH: 123 }, - 'codes': { - '200': 173732, - '201': 520878, - '503': 1 - }, - 'matches': 0, - 'customStats': {}, - 'concurrency': 1510, - 'pendingRequests': 1471 - }), - 'end_time': 1527533519591, - 'start_time': 1527533459591 -}; - -describe('Cassandra client tests', function() { - let sandbox; - let clientExecuteStub; - let revert; - let loggerErrorStub; - - let testId, revisionId, reportId, jobId, testType, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt, phase; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - - testId = uuid(); - revisionId = uuid(); - reportId = uuid(); - jobId = uuid(); - testType = 'testType'; - startTime = new Date('1/10/2017'); - testName = 'testName'; - testDescription = 'testDescription'; - testConfiguration = 'testConfiguration'; - notes = 'notes'; - lastUpdatedAt = Date.now(); - phase = '0'; - }); - - afterEach(() => { - sandbox.resetHistory(); - clientExecuteStub.reset(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('Init and shutdown tests', function(){ - it('it should initialize cassandra client successfully', (done) => { - try { - cassandraClient.init({ execute: clientExecuteStub }); - } catch (e) { - e.should.be.equal(undefined); - e.should.not.be.instanceOf(Error); - } - done(); - }); - }); - - describe('Insert new report', function () { - it('should succeed simple insert', function () { - clientExecuteStub.resolves({ rowLength: 1, rows: [{ '[applied]': true }] }); - let queryReport = 'INSERT INTO reports_summary(test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; - let queryLastReport = 'INSERT INTO last_reports(start_time_year,start_time_month,test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; - return cassandraClient.insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) - .then(function () { - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(queryReport); - clientExecuteStub.getCall(0).args[1][0].should.eql(testId); - clientExecuteStub.getCall(0).args[1][1].should.eql(revisionId); - clientExecuteStub.getCall(0).args[1][2].should.eql(reportId); - clientExecuteStub.getCall(1).args[0].should.eql(queryLastReport); - clientExecuteStub.getCall(1).args[1][0].should.eql(2017); - clientExecuteStub.getCall(1).args[1][1].should.eql(1); - clientExecuteStub.getCall(1).args[1][2].should.eql(testId); - clientExecuteStub.getCall(1).args[1][3].should.eql(revisionId); - clientExecuteStub.getCall(1).args[1][4].should.eql(reportId); - }); - }); - - it('should log error for failing inserting new report', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('should update report with benchmark tests ', async () => { - it('should update report with benchmark tests ', async () => { - clientExecuteStub.resolves({ rowLength: 1, rows: [{ '[applied]': false }] }); - let queryReport = 'UPDATE reports_summary SET score=?, benchmark_weights_data=? WHERE test_id=? AND report_id=?'; - await cassandraClient.updateReportBenchmark(testId, reportId, 5.3, 'some data'); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.callCount.should.eql(3); // query last report should be trig - clientExecuteStub.getCall(1).args[0].should.eql(queryReport); - clientExecuteStub.getCall(1).args[1][0].should.eql(5.3); - clientExecuteStub.getCall(1).args[1][1].should.eql('some data'); - clientExecuteStub.getCall(1).args[1][2].should.eql(testId); - clientExecuteStub.getCall(1).args[1][3].should.eql(reportId); - }); - }); - - describe('Insert report that already exist', function () { - it('should succeed simple insert', function () { - clientExecuteStub.resolves({ rowLength: 1, rows: [{ '[applied]': false }] }); - let queryReport = 'INSERT INTO reports_summary(test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; - return cassandraClient.insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) - .then(function () { - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.callCount.should.eql(1); // query last report should not be trig - clientExecuteStub.getCall(0).args[0].should.eql(queryReport); - clientExecuteStub.getCall(0).args[1][0].should.eql(testId); - clientExecuteStub.getCall(0).args[1][1].should.eql(revisionId); - clientExecuteStub.getCall(0).args[1][2].should.eql(reportId); - }); - }); - }); - - describe('Update report and verify last report updated', function () { - it('should succeed simple insert', async function () { - const phase = uuid(); - const cassandraClientLastReport = cassandraClient.__get__('updateLastReportAsync'); - clientExecuteStub.onCall(0).resolves({ rowLength: 1, rows: [{ 'start_time': '01/22/2017' }] }); - clientExecuteStub.onCall(1).resolves({ rowLength: 1 }); - let queryLastReport = 'UPDATE last_reports SET phase=?, last_updated_at=? WHERE start_time_year=? AND start_time_month=? AND start_time=? AND test_id=? AND report_id=?'; - await cassandraClientLastReport(testId, reportId, { phase, last_updated_at: lastUpdatedAt }); - - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(1).args[0].should.eql(queryLastReport); - clientExecuteStub.getCall(1).args[1][0].should.eql(phase); - clientExecuteStub.getCall(1).args[1][1].should.eql(lastUpdatedAt); - clientExecuteStub.getCall(1).args[1][2].should.eql(2017); - clientExecuteStub.getCall(1).args[1][3].should.eql(1); - clientExecuteStub.getCall(1).args[1][4].should.eql('01/22/2017'); - clientExecuteStub.getCall(1).args[1][5].should.eql(testId); - clientExecuteStub.getCall(1).args[1][6].should.eql(reportId); - }); - }); - describe('Get report', function(){ - it('should get single report', function(){ - let cassandraResponse = { rows: [REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; - return cassandraClient.getReport() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; - return cassandraClient.getReport() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get last report success', function(){ - it('should get last reports', function(){ - let cassandraResponse = { rows: [REPORT, REPORT, REPORT] }; - clientExecuteStub.onCall(0).resolves({ rows: [REPORT] }); - clientExecuteStub.onCall(1).resolves({ rows: [REPORT] }); - clientExecuteStub.onCall(2).resolves({ rows: [REPORT] }); - clientExecuteStub.resolves({ rows: [REPORT] }); - - let query = 'SELECT * FROM last_reports WHERE start_time_year=? AND start_time_month=? LIMIT ?'; - return cassandraClient.getLastReports(3) - .then(function (result) { - const date = new Date(); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(date.getFullYear()); - clientExecuteStub.getCall(0).args[1][1].should.eql(date.getMonth() + 1); - clientExecuteStub.getCall(0).args[1][2].should.eql(3); - result.should.eql(cassandraResponse.rows); - sandbox.resetHistory(); - }); - }); - }); - - describe('Get last report fail', function () { - it('should get failure from cassandra', function () { - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; - return cassandraClient.getReport() - .then(function (result) { - return Promise.reject(new Error('should not get here')); - }).catch(function (err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get reports', function(){ - it('should get multiple reports', function(){ - let cassandraResponse = { rows: [REPORT, REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM reports_summary WHERE test_id=?'; - return cassandraClient.getReports() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=?'; - return cassandraClient.getReports() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Insert new stats', function(){ - const runnerId = uuid(); - const statId = uuid(); - const statsTime = new Date().getTime(); - const phaseIndex = uuid(0); - const phaseStatus = uuid('started'); - const data = JSON.stringify({ median: 5 }); - - it('should succeed simple insert', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO reports_stats(runner_id, test_id, report_id, stats_id, stats_time, phase_index, phase_status, data) values(?,?,?,?,?,?,?,?)'; - return cassandraClient.insertStats(runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(runnerId); - clientExecuteStub.getCall(0).args[1][1].should.eql(testId); - clientExecuteStub.getCall(0).args[1][2].should.eql(reportId); - clientExecuteStub.getCall(0).args[1][3].should.eql(statId); - }); - }); - - it('should log error for failing inserting new report', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertStats(runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Subscribe Runner', function(){ - it('should subscribe runner to report', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - - let query = 'INSERT INTO report_subscribers(test_id, report_id, runner_id, phase_status) values(?,?,?,?)'; - return cassandraClient.subscribeRunner('test_id', 'report_id', 'runner_id', 'initializing') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('test_id'); - clientExecuteStub.getCall(0).args[1][1].should.eql('report_id'); - clientExecuteStub.getCall(0).args[1][2].should.eql('runner_id'); - clientExecuteStub.getCall(0).args[1][3].should.eql('initializing'); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=?'; - return cassandraClient.getReports() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Update Subscriber', function(){ - it('should update subscriber stage in report without stats', function(){ - let cassandraResponse = { rows: [REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'UPDATE report_subscribers SET phase_status=? WHERE test_id=? AND report_id=? AND runner_id=?'; - return cassandraClient.updateSubscriber('test_id', 'report_id', 'runner_id', 'intermediate') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('intermediate'); - clientExecuteStub.getCall(0).args[1][1].should.eql('test_id'); - clientExecuteStub.getCall(0).args[1][2].should.eql('report_id'); - clientExecuteStub.getCall(0).args[1][3].should.eql('runner_id'); - }); - }); - - it('should update subscriber stage in report with stats', function(){ - let cassandraResponse = { rows: [REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'UPDATE report_subscribers SET phase_status=?, last_stats=? WHERE test_id=? AND report_id=? AND runner_id=?'; - return cassandraClient.updateSubscriberWithStats('test_id', 'report_id', 'runner_id', 'intermediate', 'last_stats') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('intermediate'); - clientExecuteStub.getCall(0).args[1][1].should.eql('last_stats'); - clientExecuteStub.getCall(0).args[1][2].should.eql('test_id'); - clientExecuteStub.getCall(0).args[1][3].should.eql('report_id'); - clientExecuteStub.getCall(0).args[1][4].should.eql('runner_id'); - }); - }); - }); -}); diff --git a/tests/unit-tests/reporter/models/finalReportGenerator-test.js b/tests/unit-tests/reporter/models/finalReportGenerator-test.js index a8f119080..590c9e692 100644 --- a/tests/unit-tests/reporter/models/finalReportGenerator-test.js +++ b/tests/unit-tests/reporter/models/finalReportGenerator-test.js @@ -201,7 +201,7 @@ describe('Artillery report generator test', () => { }); describe('Bad flows - With parallelism', function () { - it('create final report fails when cassandra returns error', async () => { + it('create final report fails when sequelize returns error', async () => { databaseConnectorGetStatsStub.rejects(new Error('Database failure')); reportsManagerGetReportStub.resolves(REPORT); @@ -216,7 +216,7 @@ describe('Artillery report generator test', () => { testShouldFail.should.eql(false, 'Test action was supposed to get exception'); }); - it('create final report fails when no rows returned from cassandra ', async () => { + it('create final report fails when no rows returned from sequelize ', async () => { databaseConnectorGetStatsStub.resolves([]); reportsManagerGetReportStub.resolves(REPORT); @@ -336,4 +336,4 @@ const PARALLEL_INTERMEDIATE_ROWS = [ 'phase_index': '0', 'data': '{"timestamp":"2019-03-10T17:24:33.043Z","scenariosCreated":300,"scenariosCompleted":300,"requestsCompleted":300,"latency":{"min":59.5,"max":98.3,"median":61.3,"p95":72.9,"p99":84},"rps":{"count":300,"mean":20},"scenarioDuration":{"min":60,"max":98.9,"median":61.9,"p95":73.5,"p99":84.5},"scenarioCounts":{"Get response code 200":300},"errors":{},"codes":{"200":300},"matches":0,"customStats":{},"counters":{},"concurrency":1,"pendingRequests":1,"scenariosAvoided":0}' } -]; \ No newline at end of file +]; diff --git a/tests/unit-tests/tests/models/cassandraConnector-test.js b/tests/unit-tests/tests/models/cassandraConnector-test.js deleted file mode 100644 index d70b5b96a..000000000 --- a/tests/unit-tests/tests/models/cassandraConnector-test.js +++ /dev/null @@ -1,566 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let should = require('should'); -let cassandraClient = require('../../../../src/tests/models/database/cassandra/cassandraConnector'); -let uuid = require('uuid'); -let uuidCassandraDriver = require('cassandra-driver').types.Uuid; - -describe('Cassandra client tests', function () { - let sandbox; - let clientExecuteStub; - let loggerErrorStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(); - cassandraClient.init({ execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - }); - - describe('Insert new test tests', function () { - it('should succeed simple insert', function () { - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let revisionId = uuid.v4(); - - let query = 'INSERT INTO tests(id, name, description, type, updated_at, raw_data, artillery_json, revision_id, file_id, csv_file_id, processor_id) values(?,?,?,?,?,?,?,?,?,?,?)'; - return cassandraClient.insertTest({ scenarios: { raw_data: 'raw' } }, { json: 'artillery' }, id, revisionId) - .then(function () { - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - clientExecuteStub.getCall(0).args[1][7].should.eql(revisionId); - clientExecuteStub.getCall(0).args[1][5].should.eql(JSON.stringify({ 'scenarios': { 'raw_data': 'raw' } })); - clientExecuteStub.getCall(0).args[1][6].should.eql(JSON.stringify({ json: 'artillery' })); - }); - }); - - it('should log error for failing inserting new test', function () { - clientExecuteStub.rejects(); - return cassandraClient.insertTest({ data: 'raw' }, { json: 'artillery' }, uuid.v4(), uuid.v4()) - .catch(function () { - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get single test', function () { - it('Should get single test', function () { - let query = 'SELECT * FROM tests WHERE id = ? ORDER BY updated_at DESC limit 1'; - let date = new Date(); - let cassandraResponse = { - rows: [ - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: date, - raw_data: '{"data":"raw"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ab' - } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - should(clientExecuteStub.getCall(0).args[1]).eql([uuidCassandraDriver.fromString('c1656c48-e028-11e7-80c1-9a214cf093aa')]); - should(JSON.stringify(res)).eql(JSON.stringify(cassandraResponse.rows[0])); - }); - }); - - it('Should get error because of cassandra error', function () { - let query = 'SELECT * FROM tests WHERE id = ? ORDER BY updated_at DESC limit 1'; - clientExecuteStub.rejects(); - return cassandraClient.getTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('benchmark tests', () => { - it('should succeed insert benchmark', async () => { - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - - let query = 'INSERT INTO benchmarks(test_id,data) values(?,?)'; - await cassandraClient.insertTestBenchmark(id, JSON.stringify({ data: 'some data' })); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(uuidCassandraDriver.fromString(id)); - clientExecuteStub.getCall(0).args[1][1].should.eql(JSON.stringify({ data: 'some data' })); - }); - it('should get benchmark', async () => { - clientExecuteStub.resolves({ rows: [{ data: 'some data' }] }); - let id = uuid.v4(); - - let query = 'SELECT * FROM benchmarks WHERE test_id=?'; - await cassandraClient.getTestBenchmark(id); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - }); - }); - - describe('Delete test', function () { - it('Should delete single test successfully', () => { - let query = 'DELETE FROM tests WHERE id=?'; - let cassandraResponse = {}; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.deleteTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1].should.eql(['c1656c48-e028-11e7-80c1-9a214cf093aa']); - res.should.eql(cassandraResponse); - }); - }); - it('Should get error because of cassandra error', function () { - let query = 'DELETE FROM tests WHERE id=?'; - clientExecuteStub.rejects(); - return cassandraClient.deleteTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get all test revisions', function () { - it('Should get test revisions', function () { - let query = 'SELECT * FROM tests WHERE id = ?'; - let date = new Date(); - let laterDate = new Date(); - let cassandraResponse = { - rows: [ - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: date, - raw_data: '{"data":"raw"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ab' - }, - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: laterDate, - raw_data: '{"data":"raw"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ac' - } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getAllTestRevisions('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - res.should.eql(cassandraResponse.rows); - }).catch(function (err) { - throw new Error('Should not get here: ' + err); - }); - }); - - it('Should get error because of cassandra error', function () { - let query = 'SELECT * FROM tests WHERE id = ?'; - clientExecuteStub.rejects(); - return cassandraClient.getAllTestRevisions('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get all tests', function () { - it('Should get all tests', function () { - let query = 'SELECT * FROM tests'; - let date = new Date(); - let laterDate = new Date(); - let cassandraResponse = { - rows: [ - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: date, - raw_data: '{"name":"Test1","description":"Test1"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ab' - }, - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093ab', - updated_at: laterDate, - raw_data: '{"name":"Test2","description":"Test2"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ac' - } - ] - }; - - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getTests() - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - res.should.eql(cassandraResponse.rows); - }).catch(function (err) { - throw new Error('Should not get here: ' + err); - }); - }); - - it('Should get error because of cassandra error', function () { - let query = 'SELECT * FROM tests'; - clientExecuteStub.rejects(); - return cassandraClient.getTests() - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('Get dsl definition', function () { - it('Should get definition object', function () { - const cassandraResponse = { - rows: [ - { artillery_json: '{"json":"artillery"}' } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql({ - 'artillery_json': { - 'json': 'artillery' - } - }); - }); - }); - it('Should get definition undefined when there is no result', function () { - const cassandraResponse = { - rows: [] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(undefined); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.getDslDefinition('dslName', 'definitionName') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('Get dsl definitions', function () { - it('Should get array of definition object', function () { - const cassandraResponse = { - rows: [ - { artillery_json: '{"json":"artillery"}' }, - { artillery_json: '{"json":"artillery2"}' } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinitions('dslName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ?', - [ - 'dslName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql([ - { - 'artillery_json': { - 'json': 'artillery' - } - }, - { - 'artillery_json': { - 'json': 'artillery2' - } - } - ]); - }); - }); - it('Should get empty array when there is no result', function () { - const cassandraResponse = { - rows: [] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinitions('dslName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ?', - [ - 'dslName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql([]); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.getDslDefinitions('dslName') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('SELECT * FROM dsl WHERE dsl_name = ?'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('update dsl definition', function () { - it('Should get true when update applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': true } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.updateDslDefinition('dslName', 'definitionName', { json: 'artillery' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - '{"json":"artillery"}', - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(true); - }); - }); - it('Should get false when update does not applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': false } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.updateDslDefinition('dslName', 'definitionName', { json: 'artillery' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - '{"json":"artillery"}', - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(false); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.updateDslDefinition('dslName', 'definitionName', { json: 'artillery' }) - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('delete dsl definition', function () { - it('Should get true when delete applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': true } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.deleteDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(true); - }); - }); - it('Should get false when delete does not applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': false } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.deleteDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(false); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.deleteDefinition('dslName', 'definitionName') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('insertDslDefinition definition', function () { - it('Should get true when insert applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': true } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.insertDslDefinition('dslName', 'definitionName', { data: 'data' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS', - [ - 'dslName', - 'definitionName', - '{"data":"data"}' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(true); - }); - }); - it('Should get false when insert applied does not applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': false } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.insertDslDefinition('dslName', 'definitionName', { data: 'data' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS', - [ - 'dslName', - 'definitionName', - '{"data":"data"}' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(false); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.insertDslDefinition('dslName', 'definitionName', { data: 'data' }) - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); -}); diff --git a/tests/unit-tests/tests/models/database-test.js b/tests/unit-tests/tests/models/database-test.js deleted file mode 100644 index 60f4a54e3..000000000 --- a/tests/unit-tests/tests/models/database-test.js +++ /dev/null @@ -1,95 +0,0 @@ - -const should = require('should'), - sinon = require('sinon'), - cassandra = require('../../../../src/tests/models/database/cassandra/cassandraConnector'), - sequelizeConnector = require('../../../../src/tests/models/database/sequelize/sequelizeConnector'), - rewire = require('rewire'), - databaseConfig = require('../../../../src/config/databaseConfig'); -let database = require('../../../../src/tests/models/database'); -const functions = [ - { - functionName: 'insertTest', - args: ['testInfo', 'testJson', 'id', 'revisionId', 'processorFileId', 'csvFileId'] - }, - { - functionName: 'insertTestBenchmark', - args: ['testId', 'benchmarkData'] - }, - { - functionName: 'getTestBenchmark', - args: ['testId'] - }, - { - functionName: 'getTest', - args: ['id'] - }, - { - functionName: 'getTests', - args: [] - }, - { - functionName: 'deleteTest', - args: ['id'] - }, - { - functionName: 'insertDslDefinition', - args: ['dslName', 'definitionName', 'data'] - }, { - functionName: 'getDslDefinition', - args: ['dslName', 'definitionName'] - }, { - functionName: 'getDslDefinitions', - args: ['dslName'] - }, - { - functionName: 'updateDslDefinition', - args: ['dslName', 'definitionName', 'data'] - }, - { - functionName: 'deleteDefinition', - args: ['dslName', 'definitionName'] - } -]; - -describe('Testing database', function () { - let sandbox; - before(function () { - process.env.DATABASE_TYPE = 'CASSANDRA'; - sandbox = sinon.sandbox.create(); - functions.forEach(function (func) { - sandbox.stub(cassandra, func.functionName); - sandbox.stub(sequelizeConnector, func.functionName); - }); - }); - beforeEach(function () { - sandbox.resetHistory(); - }); - after(function () { - sandbox.restore(); - }); - - describe('when database type is cassandra - should applied functions on cassandra client', function () { - before(async function () { - databaseConfig.type = 'cassandra'; - database = rewire('../../../../src/tests/models/database'); - }); - functions.forEach(function (func) { - it(`checking func: ${func.functionName}`, async function () { - await database[func.functionName](...func.args); - should(cassandra[func.functionName].args).eql([func.args]); - }); - }); - }); - describe('when database type is not cassandra - should applied functions on sequlize client', function () { - before(async function () { - databaseConfig.type = 'not-cassandra'; - database = rewire('../../../../src/tests/models/database'); - }); - functions.forEach(function (func) { - it(`checking func: ${func.functionName}`, async function () { - await database[func.functionName](...func.args); - should(sequelizeConnector[func.functionName].args).eql([func.args]); - }); - }); - }); -}); From 6e9a20d3d46bd762f2151ae3e0183de74fc56674 Mon Sep 17 00:00:00 2001 From: syncush Date: Sat, 29 Aug 2020 17:55:39 +0300 Subject: [PATCH 36/38] refactor!(server): drop Cassandra support --- .circleci/config.yml | 7 - .github/ISSUE_TEMPLATE/bug_report.md | 2 +- README.md | 2 +- docs/devguide/docs/about.md | 4 +- docs/devguide/docs/configuration.md | 12 +- docs/devguide/docs/index.html.bk | 48 +- docs/devguide/docs/project.mobirise | 4 +- package-lock.json | 182 ++---- package.json | 2 - src/config/databaseConfig.js | 13 - .../database/cassandra/cassandraConnector.js | 75 --- .../models/database/databaseConnector.js | 5 +- src/database/cassandra-handler/cassandra.js | 71 --- .../cassandra-handler/cassandraMigration.js | 247 -------- .../cassandra_config_template.json | 18 - .../10__add_file_id_test_table.cql | 1 - .../init-scripts/11__create_files_table.cql | 4 - .../12__drop_last_reports_view.cql | 1 - .../13__create_last_reports_table.cql | 17 - .../14__add_enabled_jobs_table_.cql | 1 - .../14__create_processors_table.cql | 12 - .../15__create_processors_mapping_table.cql | 6 - .../17__processors_exported_functions.cql | 1 - .../18__bench_mark_data_tests.cql | 4 - .../19__bench_mark_data_reports.cql | 8 - .../init-scripts/1__create_jobs_table.cql | 15 - .../init-scripts/20__csv_file.cql | 3 - .../2__create_reports_summary_table.cql | 14 - .../3__create_reports_stats_table.cql | 10 - .../4__create_reports_subscribers_table.cql | 7 - ...5__create_last_reports_view_deprecated.cql | 5 - .../init-scripts/6__create_tests.cql | 12 - .../init-scripts/7__create_config_table.cql | 4 - .../init-scripts/8__dsl_table.cql | 7 - .../9__add_proxy_and_debug_jobs_table_.cql | 3 - src/database/database.js | 7 +- src/env.js | 11 +- src/files/models/database.js | 6 +- .../database/cassandra/cassandraConnector.js | 48 -- .../database/cassandra/cassandraConnector.js | 98 --- src/jobs/models/database/databaseConnector.js | 6 +- .../database/cassandra/cassandraConnector.js | 114 ---- .../models/database/databaseConnector.js | 4 +- .../database/cassandra/cassandraConnector.js | 244 -------- src/reports/models/databaseConnector.js | 4 +- src/tests/models/database.js | 6 +- .../database/cassandra/cassandraConnector.js | 153 ----- src/tests/models/dsl.js | 6 +- .../configurations/cassandraConfiguration.sh | 8 - tests/configurations/dockerRun.sh | 25 +- tests/integration-tests/runLocal.sh | 3 - .../cassandraHandler-test.js | 509 ---------------- .../configManager/cassandra/cassandra-test.js | 128 ---- .../configManager/configHandler-test.js | 24 +- .../configHandlerEnvVaribles-test.js | 6 +- .../sequelize/sequelizeConnector-test.js | 4 +- tests/unit-tests/env-test.js | 2 +- .../files/models/cassandraConnector-test.js | 79 --- .../unit-tests/files/models/database-test.js | 16 +- .../jobs/cassandra/cassandra-test.js | 230 ------- .../unit-tests/jobs/models/jobManager-test.js | 152 ++--- .../processors/cassandra/cassandra-test.js | 242 -------- .../reporter/cassandra/cassandra-test.js | 385 ------------ .../models/finalReportGenerator-test.js | 6 +- .../tests/models/cassandraConnector-test.js | 566 ------------------ .../unit-tests/tests/models/database-test.js | 16 +- 66 files changed, 212 insertions(+), 3723 deletions(-) delete mode 100644 src/configManager/models/database/cassandra/cassandraConnector.js delete mode 100644 src/database/cassandra-handler/cassandra.js delete mode 100644 src/database/cassandra-handler/cassandraMigration.js delete mode 100644 src/database/cassandra-handler/cassandra_config_template.json delete mode 100644 src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/11__create_files_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql delete mode 100644 src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql delete mode 100644 src/database/cassandra-handler/init-scripts/14__create_processors_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql delete mode 100644 src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql delete mode 100644 src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql delete mode 100644 src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/20__csv_file.cql delete mode 100644 src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql delete mode 100644 src/database/cassandra-handler/init-scripts/6__create_tests.cql delete mode 100644 src/database/cassandra-handler/init-scripts/7__create_config_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/8__dsl_table.cql delete mode 100644 src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql delete mode 100644 src/files/models/database/cassandra/cassandraConnector.js delete mode 100644 src/jobs/models/database/cassandra/cassandraConnector.js delete mode 100644 src/processors/models/database/cassandra/cassandraConnector.js delete mode 100644 src/reports/models/database/cassandra/cassandraConnector.js delete mode 100644 src/tests/models/database/cassandra/cassandraConnector.js delete mode 100755 tests/configurations/cassandraConfiguration.sh delete mode 100644 tests/unit-tests/cassandra-handler/cassandraHandler-test.js delete mode 100644 tests/unit-tests/configManager/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/files/models/cassandraConnector-test.js delete mode 100644 tests/unit-tests/jobs/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/processors/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/reporter/cassandra/cassandra-test.js delete mode 100644 tests/unit-tests/tests/models/cassandraConnector-test.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 0cb30450c..cbc3e78fe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,7 +33,6 @@ jobs: integration-tests: docker: - image: circleci/node:12.16 - - image: circleci/cassandra:3.10 environment: MAX_HEAP_SIZE: 2048m HEAP_NEWSIZE: 512m @@ -58,12 +57,6 @@ jobs: environment: DATABASE_TYPE: sqlite JOB_PLATFORM: docker - - run: - name: Integration tests with kubernetes and cassandra configuration - command: npm run integration-tests - environment: - DATABASE_TYPE: cassandra - JOB_PLATFORM: kubernetes - run: name: Integration tests with kubernetes and mysql configuration command: npm run integration-tests diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 42f4c7ad1..39fbfc25b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,7 +26,7 @@ If applicable, add screenshots to help explain your problem. **Version:** - Predator: [e.g. 1.0.8] - Predator-runner [e.g. 1.0.5] - - Database [e.g. cassandra] + - Database [e.g. Sqlite] **Additional context** Add any other context about the problem here. diff --git a/README.md b/README.md index c2f805f32..10ad9c10e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ |-------------------------------- |:------------------:|:---------| | Distributed Load | :sparkle: |Predator supports an unlimited number of load generators that produce multiple load runners concurrently. | Rich UI | :sparkle: |Predator offers a rich UI where you can write tests, run them and compare results. -| Reports && Tests Persistence | :sparkle: |Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE. +| Reports && Tests Persistence | :sparkle: |Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE. | Real time reports | :sparkle: |Predator aggregates all concurrent runs into a single beautiful report in real time (latency, rps, status codes and more). | CSV Datasets | :sparkle: |Predator support uploading files like csv to provide dataset for test inputs | Scheduled runs | :sparkle: |Predator can run recurring tests using cron expressions. diff --git a/docs/devguide/docs/about.md b/docs/devguide/docs/about.md index 01253769d..e8e802363 100644 --- a/docs/devguide/docs/about.md +++ b/docs/devguide/docs/about.md @@ -18,7 +18,7 @@ Predator is a performance platform that can be configured to automatically load - **One click installation**: Predator can be installed with just one click in Kubernetes and DC/OS, or on any other machine running Docker. -- **Supports 5 Different databases**: Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE. +- **Supports 5 Different databases**: Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE. - **Scheduled jobs**: Predator can run recurring tests using cron expressions. @@ -37,5 +37,3 @@ load engine to fire the requests. The schema for creating tests via the Predator ## Feature Comparison ![Screenshot](images/features.png) - - diff --git a/docs/devguide/docs/configuration.md b/docs/devguide/docs/configuration.md index b06ebfe61..7cc316efc 100644 --- a/docs/devguide/docs/configuration.md +++ b/docs/devguide/docs/configuration.md @@ -22,7 +22,7 @@ Below are variables Predator can be configured with. ## Database | Environment Variable | Description | Configurable from UI/API | Default value | |---------------------- |--------------------------------------------------------------------------------- |-------------------------- |--------------- | -| DATABASE_TYPE | Database to integrate Predator with [Cassandra, Postgres, MySQL, MSSQL, SQLITE] | x | SQLITE | +| DATABASE_TYPE | Database to integrate Predator with [Postgres, MySQL, MSSQL, SQLITE] | x | SQLITE | | DATABASE_NAME | Database/Keyspace name | x | | | DATABASE_ADDRESS | Database address | x | | | DATABASE_USERNAME | Database username | x | | @@ -30,14 +30,6 @@ Below are variables Predator can be configured with. Additional parameters for the following chosen databases: -#### Cassandra -| Environment Variable | Configurable from UI/API | Default value | -|------------------------------ |-------------------------- |---------------- | -| CASSANDRA_REPLICATION_FACTOR | x | 1 | -| CASSANDRA_CONSISTENCY | x | localQuorum | -| CASSANDRA_KEY_SPACE_STRATEGY | x | SimpleStrategy | -| CASSANDRA_LOCAL_DATA_CENTER | x | | - #### SQLITE | Environment Variable | Description | Configurable from UI/API | Default value | |---------------------- |------------------ |-------------------------- |--------------- | @@ -109,4 +101,4 @@ Additional parameters for the following chosen databases: | SMTP_PASSWORD | smtp_server.password | SMTP password | ✓ | | | SMTP_TIMEOUT | smtp_server.timeout | How many milliseconds to wait for the connection to establish to SMTP server | ✓ | 200 | | SMTP_SECURE | smtp_server.secure | if true the connection will use TLS when connecting to server. [Nodemailer SMTP options](https://nodemailer.com/smtp/) | ✓ | false | -| SMTP_REJECT_UNAUTH_CERTS | smtp_server.rejectUnauthCerts | should fail or succeed on unauthorized certificate | ✓ | false | \ No newline at end of file +| SMTP_REJECT_UNAUTH_CERTS | smtp_server.rejectUnauthCerts | should fail or succeed on unauthorized certificate | ✓ | false | diff --git a/docs/devguide/docs/index.html.bk b/docs/devguide/docs/index.html.bk index f5306ecfe..1fb17b15b 100644 --- a/docs/devguide/docs/index.html.bk +++ b/docs/devguide/docs/index.html.bk @@ -28,28 +28,28 @@ - + @@ -162,7 +162,7 @@

Supports 5 storage backends

-

Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE.

+

Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE.

@@ -273,4 +273,4 @@ - \ No newline at end of file + diff --git a/docs/devguide/docs/project.mobirise b/docs/devguide/docs/project.mobirise index 7590e2c56..abcf3dea8 100644 --- a/docs/devguide/docs/project.mobirise +++ b/docs/devguide/docs/project.mobirise @@ -506,7 +506,7 @@ } }, "_name": "features5", - "_customHTML": "
\n \n \n \n \n \n \n \n \n \n\n
\n \n \n \n
\n \n \n \n \n
\n\n
\n
\n
\n
\n\n
\n
\n \n
\n
\n

\n Built for the cloud

\n

Predator is built to take advantage of Kubernetes and DC/OS. It’s integrated with those platforms and is able to manage the load generator lifecycles by itself.

\n
\n
\n\n
1\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n Distributed load

\n

Predator supports an unlimited number of load generators that produce multiple load runners concurrently. Predator will aggregate the result of those runners in real-time into a beautiful report.

\n
\n
\n\n
2\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n One click installation

\n

Predator can be installed with just one click in Kubernetes and DC/OS, or on any other machine running Docker.

\n
\n
\n\n
3\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

Supports 5 storage backends

\n

Predator provides out-of-the box functionality for persisting data in Cassandra, Postgres, MySQL, MSSQL and SQLITE.

\n
\n
\n
\n
\n
", + "_customHTML": "
\n \n \n \n \n \n \n \n \n \n\n
\n \n \n \n
\n \n \n \n \n
\n\n
\n
\n
\n
\n\n
\n
\n \n
\n
\n

\n Built for the cloud

\n

Predator is built to take advantage of Kubernetes and DC/OS. It’s integrated with those platforms and is able to manage the load generator lifecycles by itself.

\n
\n
\n\n
1\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n Distributed load

\n

Predator supports an unlimited number of load generators that produce multiple load runners concurrently. Predator will aggregate the result of those runners in real-time into a beautiful report.

\n
\n
\n\n
2\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

\n One click installation

\n

Predator can be installed with just one click in Kubernetes and DC/OS, or on any other machine running Docker.

\n
\n
\n\n
3\" mbr-class=\"{'col-lg-3': cardsAmount == '4',\n 'col-lg-4': cardsAmount == '3'}\">\n
\n \n
\n
\n

Supports 5 storage backends

\n

Predator provides out-of-the box functionality for persisting data in Postgres, MySQL, MSSQL and SQLITE.

\n
\n
\n
\n
\n
", "_cid": "rqyVDU3s7P", "_anchor": "features5-7", "_protectedParams": [], @@ -1309,4 +1309,4 @@ ] } } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 7227f0694..4940ecd14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,7 +155,7 @@ }, "@babel/runtime": { "version": "7.11.2", - "resolved": "http://npm.zooz.co:8083/@babel%2fruntime/-/runtime-7.11.2.tgz", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "dev": true, "requires": { @@ -279,13 +279,13 @@ }, "ansi-regex": { "version": "5.0.0", - "resolved": "http://npm.zooz.co:8083/ansi-regex/-/ansi-regex-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { "version": "4.2.1", - "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-4.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { @@ -295,13 +295,13 @@ }, "camelcase": { "version": "5.3.1", - "resolved": "http://npm.zooz.co:8083/camelcase/-/camelcase-5.3.1.tgz", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "chalk": { "version": "4.1.0", - "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { @@ -311,7 +311,7 @@ }, "cliui": { "version": "6.0.0", - "resolved": "http://npm.zooz.co:8083/cliui/-/cliui-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { @@ -322,7 +322,7 @@ }, "color-convert": { "version": "2.0.1", - "resolved": "http://npm.zooz.co:8083/color-convert/-/color-convert-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { @@ -331,13 +331,13 @@ }, "color-name": { "version": "1.1.4", - "resolved": "http://npm.zooz.co:8083/color-name/-/color-name-1.1.4.tgz", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "core-js": { "version": "3.6.5", - "resolved": "http://npm.zooz.co:8083/core-js/-/core-js-3.6.5.tgz", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", "dev": true }, @@ -356,13 +356,13 @@ }, "emoji-regex": { "version": "8.0.0", - "resolved": "http://npm.zooz.co:8083/emoji-regex/-/emoji-regex-8.0.0.tgz", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "find-up": { "version": "4.1.0", - "resolved": "http://npm.zooz.co:8083/find-up/-/find-up-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { @@ -372,19 +372,19 @@ }, "has-flag": { "version": "4.0.0", - "resolved": "http://npm.zooz.co:8083/has-flag/-/has-flag-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "http://npm.zooz.co:8083/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "locate-path": { "version": "5.0.0", - "resolved": "http://npm.zooz.co:8083/locate-path/-/locate-path-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { @@ -393,7 +393,7 @@ }, "p-locate": { "version": "4.1.0", - "resolved": "http://npm.zooz.co:8083/p-locate/-/p-locate-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { @@ -402,7 +402,7 @@ }, "parse-json": { "version": "5.0.1", - "resolved": "http://npm.zooz.co:8083/parse-json/-/parse-json-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", "dev": true, "requires": { @@ -414,19 +414,19 @@ }, "path-exists": { "version": "4.0.0", - "resolved": "http://npm.zooz.co:8083/path-exists/-/path-exists-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-type": { "version": "4.0.0", - "resolved": "http://npm.zooz.co:8083/path-type/-/path-type-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, "string-width": { "version": "4.2.0", - "resolved": "http://npm.zooz.co:8083/string-width/-/string-width-4.2.0.tgz", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { @@ -437,7 +437,7 @@ }, "strip-ansi": { "version": "6.0.0", - "resolved": "http://npm.zooz.co:8083/strip-ansi/-/strip-ansi-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { @@ -446,7 +446,7 @@ }, "supports-color": { "version": "7.1.0", - "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-7.1.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { @@ -455,7 +455,7 @@ }, "wrap-ansi": { "version": "6.2.0", - "resolved": "http://npm.zooz.co:8083/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { @@ -466,7 +466,7 @@ }, "yargs": { "version": "15.4.1", - "resolved": "http://npm.zooz.co:8083/yargs/-/yargs-15.4.1.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { @@ -485,7 +485,7 @@ }, "yargs-parser": { "version": "18.1.3", - "resolved": "http://npm.zooz.co:8083/yargs-parser/-/yargs-parser-18.1.3.tgz", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { @@ -547,7 +547,7 @@ }, "ansi-styles": { "version": "4.2.1", - "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-4.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { @@ -557,7 +557,7 @@ }, "chalk": { "version": "4.1.0", - "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-4.1.0.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { @@ -567,7 +567,7 @@ }, "color-convert": { "version": "2.0.1", - "resolved": "http://npm.zooz.co:8083/color-convert/-/color-convert-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { @@ -576,19 +576,19 @@ }, "color-name": { "version": "1.1.4", - "resolved": "http://npm.zooz.co:8083/color-name/-/color-name-1.1.4.tgz", + "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": "http://npm.zooz.co:8083/has-flag/-/has-flag-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.1.0", - "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-7.1.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { @@ -615,7 +615,7 @@ }, "semver": { "version": "7.3.2", - "resolved": "http://npm.zooz.co:8083/semver/-/semver-7.3.2.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } @@ -893,7 +893,7 @@ }, "path-exists": { "version": "4.0.0", - "resolved": "http://npm.zooz.co:8083/path-exists/-/path-exists-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true } @@ -951,11 +951,6 @@ "resolved": "http://npm.zooz.co:8083/@types%2fcolor-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, "@types/minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", @@ -1056,11 +1051,6 @@ "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", "dev": true }, - "adm-zip": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", - "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==" - }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", @@ -1087,7 +1077,7 @@ }, "ansi-colors": { "version": "3.2.3", - "resolved": "http://npm.zooz.co:8083/ansi-colors/-/ansi-colors-3.2.3.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, @@ -1130,7 +1120,7 @@ }, "anymatch": { "version": "3.1.1", - "resolved": "http://npm.zooz.co:8083/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { @@ -1635,7 +1625,7 @@ }, "binary-extensions": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/binary-extensions/-/binary-extensions-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, @@ -1783,7 +1773,7 @@ }, "braces": { "version": "3.0.2", - "resolved": "http://npm.zooz.co:8083/braces/-/braces-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { @@ -2006,39 +1996,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "cassandra-driver": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/cassandra-driver/-/cassandra-driver-4.3.1.tgz", - "integrity": "sha512-4Yuf9UkmwidiXKdAb4AgkC92CcUhP4hNUNaMNnKMGA3MRMQj2ZCEL6BR0/PrMPkYGC5faMPHJ4YzWAu3FFvM6g==", - "requires": { - "@types/long": "^4.0.0", - "@types/node": ">=4", - "adm-zip": "^0.4.13", - "long": "^2.2.0" - } - }, - "cassandra-migration": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cassandra-migration/-/cassandra-migration-2.7.0.tgz", - "integrity": "sha512-TVOLANJnURNXxLgSyoyKeYoAXqrbUJdCizOsh8riCqe5wwYM6m504lOwPgPMP9rnOVx8ImX1w96GpiQwWdLB3Q==", - "requires": { - "cassandra-driver": "^3.3.0", - "commander": "^2.9", - "durations": "^3.4.1", - "lodash": "^4.13.1", - "q": "^1.4" - }, - "dependencies": { - "cassandra-driver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/cassandra-driver/-/cassandra-driver-3.6.0.tgz", - "integrity": "sha512-CkN3V+oPaF5RvakUjD3uUjEm8f6U8S0aT1+YqeQsVT3UDpPT2K8SOdNDEHA1KjamakHch6zkDgHph1xWyqBGGw==", - "requires": { - "long": "^2.2.0" - } - } - } - }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -2097,7 +2054,7 @@ }, "chokidar": { "version": "3.3.0", - "resolved": "http://npm.zooz.co:8083/chokidar/-/chokidar-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { @@ -2694,7 +2651,7 @@ }, "dot-prop": { "version": "5.2.0", - "resolved": "http://npm.zooz.co:8083/dot-prop/-/dot-prop-5.2.0.tgz", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, "requires": { @@ -2703,7 +2660,7 @@ }, "is-obj": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/is-obj/-/is-obj-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true } @@ -2721,7 +2678,7 @@ "dependencies": { "compare-func": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/compare-func/-/compare-func-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "requires": { @@ -2731,7 +2688,7 @@ }, "dot-prop": { "version": "5.2.0", - "resolved": "http://npm.zooz.co:8083/dot-prop/-/dot-prop-5.2.0.tgz", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, "requires": { @@ -2740,7 +2697,7 @@ }, "is-obj": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/is-obj/-/is-obj-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true } @@ -3052,7 +3009,7 @@ "dependencies": { "compare-func": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/compare-func/-/compare-func-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "requires": { @@ -3062,7 +3019,7 @@ }, "dot-prop": { "version": "5.2.0", - "resolved": "http://npm.zooz.co:8083/dot-prop/-/dot-prop-5.2.0.tgz", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, "requires": { @@ -3071,7 +3028,7 @@ }, "is-obj": { "version": "2.0.0", - "resolved": "http://npm.zooz.co:8083/is-obj/-/is-obj-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true } @@ -4123,11 +4080,6 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, - "durations": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/durations/-/durations-3.4.2.tgz", - "integrity": "sha512-V/lf7y33dGaypZZetVI1eu7BmvkbC4dItq12OElLRpKuaU5JxQstV2zHwLv8P7cNbQ+KL1WD80zMCTx5dNC4dg==" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -5129,7 +5081,7 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "http://npm.zooz.co:8083/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { @@ -5799,7 +5751,7 @@ "dependencies": { "dargs": { "version": "7.0.0", - "resolved": "http://npm.zooz.co:8083/dargs/-/dargs-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true } @@ -6121,7 +6073,7 @@ }, "he": { "version": "1.2.0", - "resolved": "http://npm.zooz.co:8083/he/-/he-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, @@ -6580,7 +6532,7 @@ }, "is-binary-path": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { @@ -6673,7 +6625,7 @@ }, "is-finite": { "version": "1.1.0", - "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", "dev": true }, @@ -6714,7 +6666,7 @@ }, "is-number": { "version": "7.0.0", - "resolved": "http://npm.zooz.co:8083/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, @@ -7175,7 +7127,7 @@ }, "kind-of": { "version": "6.0.3", - "resolved": "http://npm.zooz.co:8083/kind-of/-/kind-of-6.0.3.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "latest-version": { @@ -7394,11 +7346,6 @@ "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", "dev": true }, - "long": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", - "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=" - }, "longest": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-2.0.1.tgz", @@ -7561,7 +7508,7 @@ }, "yargs-parser": { "version": "18.1.3", - "resolved": "http://npm.zooz.co:8083/yargs-parser/-/yargs-parser-18.1.3.tgz", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { @@ -7745,7 +7692,7 @@ }, "minimist": { "version": "1.2.5", - "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.5.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { @@ -7827,7 +7774,7 @@ }, "mocha": { "version": "7.1.1", - "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-7.1.1.tgz", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { @@ -8356,7 +8303,7 @@ }, "node-environment-flags": { "version": "1.0.6", - "resolved": "http://npm.zooz.co:8083/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { @@ -8668,7 +8615,7 @@ }, "object.getownpropertydescriptors": { "version": "2.1.0", - "resolved": "http://npm.zooz.co:8083/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { @@ -9068,7 +9015,7 @@ }, "picomatch": { "version": "2.2.2", - "resolved": "http://npm.zooz.co:8083/picomatch/-/picomatch-2.2.2.tgz", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, @@ -9286,7 +9233,8 @@ "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true }, "qs": { "version": "6.5.2", @@ -9468,7 +9416,7 @@ }, "readdirp": { "version": "3.2.0", - "resolved": "http://npm.zooz.co:8083/readdirp/-/readdirp-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { @@ -9896,7 +9844,7 @@ }, "rewire": { "version": "5.0.0", - "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { @@ -10988,7 +10936,7 @@ }, "streamsearch": { "version": "0.1.2", - "resolved": "http://npm.zooz.co:8083/streamsearch/-/streamsearch-0.1.2.tgz", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, "string-width": { @@ -11534,7 +11482,7 @@ }, "to-regex-range": { "version": "5.0.1", - "resolved": "http://npm.zooz.co:8083/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { @@ -12133,7 +12081,7 @@ }, "yargs-unparser": { "version": "1.6.0", - "resolved": "http://npm.zooz.co:8083/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { diff --git a/package.json b/package.json index 092a38223..9df78ac76 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,6 @@ "artillery": "^1.6.0-29", "bluebird": "^3.7.2", "body-parser": "^1.19.0", - "cassandra-driver": "4.3.1", - "cassandra-migration": "^2.7.0", "copy-dir": "^0.3.0", "cron": "1.7.2", "dockerode": "^2.5.8", diff --git a/src/config/databaseConfig.js b/src/config/databaseConfig.js index 0fdb48f0f..001d66312 100644 --- a/src/config/databaseConfig.js +++ b/src/config/databaseConfig.js @@ -1,23 +1,10 @@ -let cassandra = require('cassandra-driver'); const config = { type: (process.env.DATABASE_TYPE || 'SQLITE').toUpperCase(), name: process.env.DATABASE_NAME || 'predator', address: process.env.DATABASE_ADDRESS, username: process.env.DATABASE_USERNAME, password: process.env.DATABASE_PASSWORD, - cassandraReplicationFactor: process.env.CASSANDRA_REPLICATION_FACTOR || 1, - cassandraConsistency: getCassandraConsistencyByName(process.env.CASSANDRA_CONSISTENCY), - cassandraKeyspaceStrategy: process.env.CASSANDRA_KEY_SPACE_STRATEGY || 'SimpleStrategy', - cassandraLocalDataCenter: process.env.CASSANDRA_LOCAL_DATA_CENTER || 'datacenter1', sqliteStorage: process.env.SQLITE_STORAGE || 'predator' }; -function getCassandraConsistencyByName(cassandraConsistencyName) { - let consistency = cassandra.types.consistencies[cassandraConsistencyName]; - if (!consistency) { - consistency = cassandra.types.consistencies.localQuorum; - } - return consistency; -} - module.exports = config; diff --git a/src/configManager/models/database/cassandra/cassandraConnector.js b/src/configManager/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index bb59820e3..000000000 --- a/src/configManager/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,75 +0,0 @@ -const logger = require('../../../../common/logger'); -const databaseConfig = require('../../../../config/databaseConfig'); -const GET_CONFIG_VALUE = 'SELECT* FROM config WHERE key= ?'; -const GET_CONFIG = 'SELECT* FROM config'; -const INSERT_DATA = 'INSERT INTO config(key, value) values(?,?)'; -const DELETE_CONFIG = 'DELETE FROM config WHERE key=?;'; - -let client; - -module.exports = { - init, - updateConfig, - getConfig, - deleteConfig, - getConfigValue -}; -function init(cassandraClient) { - client = cassandraClient; -} - -const queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -function updateConfig(updateValues) { - let queriesArr = []; - Object.keys(updateValues).forEach(key => { - let value = updateValues[key] instanceof Object ? JSON.stringify(updateValues[key]) : updateValues[key] + ''; - queriesArr.push({ 'query': INSERT_DATA, 'params': [key, value] }); - }); - return batchUpsert(queriesArr, queryOptions); -} - -function deleteConfig(key) { - return executeQuery(DELETE_CONFIG, [key]); -} - -function getConfigValue(configValue) { - return executeQuery(GET_CONFIG_VALUE, [configValue]); -} - -function getConfig() { - return executeQuery(GET_CONFIG); -} - -async function batchUpsert(queriesArr, queryOptions) { - try { - const result = await client.batch(queriesArr, queryOptions); - logger.trace('Query result', { - queryArr: queriesArr, - queryOptions: queryOptions, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - } catch (exception) { - logger.error(`Cassandra batch failed \n ${JSON.stringify({ queriesArr, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - } -} - -async function executeQuery(query, params) { - try { - let result = await client.execute(query, params, { prepare: true }, queryOptions); - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - } catch (exception) { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - } -} \ No newline at end of file diff --git a/src/configManager/models/database/databaseConnector.js b/src/configManager/models/database/databaseConnector.js index 26b5b591b..62f6c96e3 100644 --- a/src/configManager/models/database/databaseConnector.js +++ b/src/configManager/models/database/databaseConnector.js @@ -1,10 +1,7 @@ 'use strict'; - -const databaseConfig = require('../../../config/databaseConfig'); -const cassandraConnector = require('./cassandra/cassandraConnector'); const sequelizeConnector = require('./sequelize/sequelizeConnector'); -const databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +const databaseConnector = sequelizeConnector; module.exports = { init, diff --git a/src/database/cassandra-handler/cassandra.js b/src/database/cassandra-handler/cassandra.js deleted file mode 100644 index ace1d11f4..000000000 --- a/src/database/cassandra-handler/cassandra.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const schedulerCassandraConnector = require('../../jobs/models/database/cassandra/cassandraConnector'); -const reportsCassandraConnector = require('../../reports/models/database/cassandra/cassandraConnector'); -const testsCassandraConnector = require('../../tests/models/database/cassandra/cassandraConnector'); -const configCassandraConnector = require('../../configManager/models/database/cassandra/cassandraConnector'); -const processorsCassandraConnector = require('../../processors/models/database/cassandra/cassandraConnector'); -const files = require('../../files/models/database/cassandra/cassandraConnector'); -const databaseConfig = require('../../config/databaseConfig'); -const cassandraMigration = require('./cassandraMigration'); -const logger = require('../../common/logger'); -const cassandra = require('cassandra-driver'); -let cassandraClient; - -module.exports.init = async () => { - cassandraClient = await createClient(); - await cassandraMigration.runMigration(); - await reportsCassandraConnector.init(cassandraClient); - await schedulerCassandraConnector.init(cassandraClient); - await testsCassandraConnector.init(cassandraClient); - await configCassandraConnector.init(cassandraClient); - await processorsCassandraConnector.init(cassandraClient); - await files.init(cassandraClient); - logger.info('cassandra client initialized'); -}; - -module.exports.ping = () => { - const query = 'SELECT * FROM system_schema.keyspaces where keyspace_name=?'; - let queryParams = [databaseConfig.name]; - return cassandraClient.execute(query, queryParams) - .then(function (results) { - if (!results.rows || results.rows.length <= 0) { - return Promise.reject(new Error('Key space wasn\'t found')); - } else { - return Promise.resolve(true); - } - }).catch(function () { - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -}; - -module.exports.closeConnection = () => { - return new Promise((resolve, reject) => { - if (cassandraClient) { - try { - cassandraClient.shutdown(); - logger.info('Cassandra client shutdown successful'); - return resolve(); - } catch (exception) { - logger.error('Failed to close Cassandra connections' + exception); - return reject(exception); - } - } else { - logger.info('Cassandra client shutdown successful'); - return resolve(); - } - }); -}; - -async function createClient() { - const authProvider = new cassandra.auth.PlainTextAuthProvider(databaseConfig.username, databaseConfig.password); - const config = { - contactPoints: String(databaseConfig.address).split(','), - keyspace: databaseConfig.name, - authProvider, - localDataCenter: databaseConfig.cassandraLocalDataCenter - }; - - let cassandraClient = new cassandra.Client(config); - return cassandraClient; -} diff --git a/src/database/cassandra-handler/cassandraMigration.js b/src/database/cassandra-handler/cassandraMigration.js deleted file mode 100644 index 3c90be75c..000000000 --- a/src/database/cassandra-handler/cassandraMigration.js +++ /dev/null @@ -1,247 +0,0 @@ -'use strict'; - -let cassandra = require('cassandra-driver'), - cassandraConfig = require('../../config/databaseConfig'), - client; -let args; -let fs = require('fs-extra'); -let cmd = require('node-cmd'); -let logger = require('../../common/logger'); -let path = require('path'); -let CREATE_KEY_SPACE_QUERY; -const CONSISTENCY_POLICY = cassandra.types.consistencies.localQuorum; -const MAX_FETCH_SIZE = 1000; -const isDevMode = process.env.DEV_MODE === 'true'; - -let initFileNameTemplate = 'cassandra_config_template.json', - initFileName = 'cassandra_config.json'; - -let cassandraHandlerLogContext = { - 'key_space_name': cassandraConfig.name, - 'initFileNameTemplate': initFileNameTemplate, - 'init_file_name': initFileName -}; - -const options = { - prepare: true, - consistency: cassandra.types.consistencies.localQuorum -}; - -module.exports.initArgs = initArgs; -module.exports.closeCassandraConnection = closeCassandraConnection; -module.exports.initCassandraConnection = initCassandraConnection; - -module.exports.runMigration = function () { - initArgs(); - - return initCassandraConnection() - .then(function () { - if (args.cassandra_keyspace_strategy === 'SimpleStrategy') { - CREATE_KEY_SPACE_QUERY = 'CREATE KEYSPACE IF NOT EXISTS ' + - args.key_space_name + - " WITH replication = {'class': 'SimpleStrategy', 'replication_factor':" + - args.replication_factor + '}'; - } else if (args.cassandra_keyspace_strategy === 'NetworkTopologyStrategy') { - CREATE_KEY_SPACE_QUERY = 'CREATE KEYSPACE IF NOT EXISTS ' + - args.key_space_name + - " WITH replication = {'class': 'NetworkTopologyStrategy', '" + args.cassandra_local_data_center + "' :" + - args.replication_factor + '}'; - } else { - throw new Error(args.cassandra_keyspace_strategy + ' is not supported cassandra keyspace strategy'); - } - return createKeySpaceIfNeeded(); - }) - .then(function () { - return closeCassandraConnection(); - }) - .then(function () { - return createConfigTemplateFile(); - }) - .then(function () { - if (!isDevMode) { - return runCassandraScripts(); - } - }) - .then(function () { - return removeConfigFile(); - }) - .then(function () { - initKeyspaceCassandraConnection(args.key_space_name); - cassandraHandlerLogContext = { - 'key_space_name': cassandraConfig.name - }; - }) - .catch(function (error) { - cassandraHandlerLogContext.initialize_cassandra_environment_error = error; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: error occurred while trying to init cassandra credentials'); - process.exit(1); - }); -}; - -module.exports.ping = function (keyspace) { - return new Promise(function (resolve, reject) { - const query = 'SELECT * FROM system_schema.keyspaces where keyspace_name=?'; - let queryParams = [keyspace]; - - client.execute(query, queryParams, options) - .then(function (results) { - if (!results.rows || results.rows.length <= 0) { - return reject(new Error('Key space doesn\'t found')); - } else { - return resolve(true); - } - }).catch(function (error) { - return reject(error); - }); - }); -}; - -function initArgs() { - args = { - key_space_name: cassandraConfig.name, - cassandra_url: cassandraConfig.address, - replication_factor: cassandraConfig.cassandraReplicationFactor, - cassandra_username: cassandraConfig.username, - cassandra_password: cassandraConfig.password, - cassandra_keyspace_strategy: cassandraConfig.cassandraKeyspaceStrategy, - cassandra_local_data_center: cassandraConfig.cassandraLocalDataCenter, - root_dir: path.join(__dirname, '../../') - }; -} - -function closeCassandraConnection() { - logger.info(cassandraHandlerLogContext, 'Cassandra handler: closing cassandra connection'); - return new Promise(function (resolve, reject) { - if (client) { - client.shutdown(function (err) { - if (err) { - cassandraHandlerLogContext.client_shutdown_err = err; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: failed to close Cassandra connection.'); - reject(err); - } else { - logger.info(cassandraHandlerLogContext, 'Cassandra handler: connection was closed successfully'); - resolve(); - } - }); - } else { - resolve(); - } - }); -} - -function runCassandraScripts() { - return new Promise(function (resolve, reject) { - let initCmd = path.join(args.root_dir, '../node_modules/.bin/cassandra-migration'); - let initConfigPath = path.join(args.root_dir, '/database/cassandra-handler', initFileName); - logger.info(cassandraHandlerLogContext, 'Cassandra handler: running migration scripts'); - - cmd.get( - initCmd + ' ' + initConfigPath, - function (err, data, stderr) { - if (err) { - cassandraHandlerLogContext.run_cassandra_scripts_err = err || stderr; - cassandraHandlerLogContext.run_cassandra_scripts_stderr = stderr; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: failed running cassandra migration scripts'); - reject(err || stderr); - } - cassandraHandlerLogContext.run_cassandra_scripts_output = data; - logger.info(cassandraHandlerLogContext, 'Cassandra handler: successfully ran cassandra migration scripts'); - delete cassandraHandlerLogContext.run_cassandra_scripts_output; - resolve(); - } - ); - }); -} - -function removeConfigFile() { - return new Promise(function (resolve, reject) { - logger.trace(cassandraHandlerLogContext, 'Cassandra handler: removiung cassandra migration config file'); - fs.remove(path.join(args.root_dir, '/database/cassandra-handler/', initFileName), function (err) { - if (err) { - cassandraHandlerLogContext.remove_config_file_err = err; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: could not remove cassandra migration config file'); - reject(err); - } - - logger.info(cassandraHandlerLogContext, 'Cassandra handler: successfully removed cassandra migration config file'); - resolve(); - }); - }); -} - -function createConfigTemplateFile() { - return new Promise(function (resolve, reject) { - let templateJsonFile = require('./' + initFileNameTemplate); - - templateJsonFile.migrationsDir = path.join(args.root_dir, '/database/cassandra-handler/init-scripts'); - templateJsonFile.cassandra.contactPoints = args.cassandra_url.split(','); - templateJsonFile.cassandra.keyspace = args.key_space_name; - templateJsonFile.auth.username = args.cassandra_username; - templateJsonFile.auth.password = args.cassandra_password; - - logger.trace(cassandraHandlerLogContext, 'Cassandra handler: set init templateJsonFile'); - - fs.writeFile(path.join(args.root_dir, 'database/cassandra-handler/', initFileName), JSON.stringify(templateJsonFile, null, 2), function (err) { - if (err) { - cassandraHandlerLogContext.remove_config_template_file_err = err; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: could not write to cassandra init file'); - reject(err); - } - logger.info(cassandraHandlerLogContext, 'Cassandra handler: successfully wrote to cassandra init file'); - resolve(); - }); - }); -} - -function initCassandraConnection() { - return new Promise(function (resolve, reject) { - client = new cassandra.Client(buildClient(undefined)); - resolve(); - }); -} - -function initKeyspaceCassandraConnection(keyspace) { - return new Promise(function (resolve, reject) { - client = new cassandra.Client(buildClient(keyspace)); - - logger.info(cassandraHandlerLogContext, 'Cassandra Handler: Successfully connected to cassandra keyspace'); - resolve(); - }); -} - -function buildClient(keyspace) { - let authProvider = new cassandra.auth.PlainTextAuthProvider(args.cassandra_username, args.cassandra_password); - let cassandraClient = { - contactPoints: args.cassandra_url.split(','), - authProvider: authProvider, - localDataCenter: args.cassandra_local_data_center - }; - - if (keyspace) { - cassandraClient['keyspace'] = keyspace; - } else { - cassandraClient['queryOptions'] = { - consistency: CONSISTENCY_POLICY, - fetchSize: MAX_FETCH_SIZE - }; - } - - logger.trace(cassandraHandlerLogContext, 'Cassandra Handler: set client configuration'); - return cassandraClient; -} - -function createKeySpaceIfNeeded() { - return new Promise(function (resolve, reject) { - client.execute(CREATE_KEY_SPACE_QUERY, null, { prepare: true }, function (err) { - if (err) { - cassandraHandlerLogContext.create_key_space_query_err = err; - cassandraHandlerLogContext.create_key_space_query_inner_err = err.innerErrors; - logger.error(cassandraHandlerLogContext, 'Cassandra handler: could not create keyspace'); - reject(err); - } else { - logger.info(cassandraHandlerLogContext, 'Cassandra handler: CREATE_KEY_SPACE_QUERY executed successfully!'); - resolve(); - } - }); - }); -} diff --git a/src/database/cassandra-handler/cassandra_config_template.json b/src/database/cassandra-handler/cassandra_config_template.json deleted file mode 100644 index 6cb357ee5..000000000 --- a/src/database/cassandra-handler/cassandra_config_template.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "migrationsDir": "./migration-scripts", - "quiet": false, - "cassandra": { - "contactPoints": [""], - "keyspace": "", - "protocolOptions": { - "port": 9042 - }, - "socketOptions": { - "connectTimeout": 15000 - } - }, - "auth": { - "username": "", - "password": "" - } - } \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql b/src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql deleted file mode 100644 index 811c7b914..000000000 --- a/src/database/cassandra-handler/init-scripts/10__add_file_id_test_table.cql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE tests ADD file_id uuid; diff --git a/src/database/cassandra-handler/init-scripts/11__create_files_table.cql b/src/database/cassandra-handler/init-scripts/11__create_files_table.cql deleted file mode 100644 index 4fe6dd733..000000000 --- a/src/database/cassandra-handler/init-scripts/11__create_files_table.cql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS files( -id text, -file text, -PRIMARY KEY (id)); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql b/src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql deleted file mode 100644 index c780e78d8..000000000 --- a/src/database/cassandra-handler/init-scripts/12__drop_last_reports_view.cql +++ /dev/null @@ -1 +0,0 @@ -DROP MATERIALIZED VIEW IF EXISTS last_reports diff --git a/src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql b/src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql deleted file mode 100644 index 14146e707..000000000 --- a/src/database/cassandra-handler/init-scripts/13__create_last_reports_table.cql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE TABLE IF NOT EXISTS last_reports( -test_id uuid, -start_time_year int, -start_time_month int, -revision_id uuid, -report_id text, -test_configuration text, -last_updated_at timestamp, -start_time timestamp, -test_name text, -test_description text, -job_id text, -test_type text, -notes text, -phase text, -PRIMARY KEY ((start_time_year,start_time_month),start_time ,report_id, test_id)) -WITH CLUSTERING ORDER BY (start_time DESC); diff --git a/src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql b/src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql deleted file mode 100644 index e248f8b58..000000000 --- a/src/database/cassandra-handler/init-scripts/14__add_enabled_jobs_table_.cql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE jobs ADD enabled boolean; \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/14__create_processors_table.cql b/src/database/cassandra-handler/init-scripts/14__create_processors_table.cql deleted file mode 100644 index bce252e2f..000000000 --- a/src/database/cassandra-handler/init-scripts/14__create_processors_table.cql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS processors -( - id uuid, - name text, - description text, - javascript text, - created_at timestamp, - updated_at timestamp, - PRIMARY KEY (id, created_at)) - WITH CLUSTERING ORDER BY (created_at DESC); ---- -ALTER TABLE tests ADD processor_id uuid; diff --git a/src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql b/src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql deleted file mode 100644 index 5f491997c..000000000 --- a/src/database/cassandra-handler/init-scripts/15__create_processors_mapping_table.cql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE IF NOT EXISTS processors_mapping -( - name text, - id uuid, - PRIMARY KEY (name) -); diff --git a/src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql b/src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql deleted file mode 100644 index 1eac2dda1..000000000 --- a/src/database/cassandra-handler/init-scripts/17__processors_exported_functions.cql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE processors ADD exported_functions list; diff --git a/src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql b/src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql deleted file mode 100644 index e8e2c444f..000000000 --- a/src/database/cassandra-handler/init-scripts/18__bench_mark_data_tests.cql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS benchmarks( -test_id uuid, -data text, -PRIMARY KEY (test_id)); diff --git a/src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql b/src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql deleted file mode 100644 index 1017357c3..000000000 --- a/src/database/cassandra-handler/init-scripts/19__bench_mark_data_reports.cql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE reports_summary ADD benchmark_weights_data text; ---- -ALTER TABLE reports_summary ADD score float; ---- -ALTER TABLE last_reports ADD benchmark_weights_data text; ---- -ALTER TABLE last_reports ADD score float; - diff --git a/src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql b/src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql deleted file mode 100644 index 62843574a..000000000 --- a/src/database/cassandra-handler/init-scripts/1__create_jobs_table.cql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE IF NOT EXISTS jobs( -id uuid, -test_id uuid, -environment text, -cron_expression text, -arrival_rate int, -duration int, -ramp_to int, -webhooks list, -emails list, -parallelism int, -max_virtual_users int, -notes text, -PRIMARY KEY (id) -); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/20__csv_file.cql b/src/database/cassandra-handler/init-scripts/20__csv_file.cql deleted file mode 100644 index d170cdb89..000000000 --- a/src/database/cassandra-handler/init-scripts/20__csv_file.cql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE files ADD name text; ---- -ALTER TABLE tests ADD csv_file_id uuid; diff --git a/src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql b/src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql deleted file mode 100644 index 30a43ab6b..000000000 --- a/src/database/cassandra-handler/init-scripts/2__create_reports_summary_table.cql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS reports_summary( -test_id uuid, -revision_id uuid, -report_id text, -test_configuration text, -last_updated_at timestamp, -start_time timestamp, -test_name text, -test_description text, -job_id text, -test_type text, -notes text, -phase text, -PRIMARY KEY (test_id, report_id)); diff --git a/src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql b/src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql deleted file mode 100644 index e8e5413e5..000000000 --- a/src/database/cassandra-handler/init-scripts/3__create_reports_stats_table.cql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS reports_stats( -runner_id text, -test_id uuid, -report_id text, -stats_id uuid, -stats_time timestamp, -phase_status text, -data text, -phase_index text, -PRIMARY KEY ((test_id, report_id), stats_time)) WITH CLUSTERING ORDER BY (stats_time ASC); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql b/src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql deleted file mode 100644 index 7f144c34b..000000000 --- a/src/database/cassandra-handler/init-scripts/4__create_reports_subscribers_table.cql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS report_subscribers( -test_id uuid, -report_id text, -runner_id text, -phase_status text, -last_stats text, -PRIMARY KEY (test_id, report_id, runner_id)); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql b/src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql deleted file mode 100644 index 2e654974d..000000000 --- a/src/database/cassandra-handler/init-scripts/5__create_last_reports_view_deprecated.cql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE MATERIALIZED VIEW last_reports_deprecated AS -SELECT * FROM reports_summary -WHERE report_id IS NOT NULL AND start_time IS NOT NULL AND test_id IS NOT NULL -PRIMARY KEY (start_time, report_id, test_id) -WITH CLUSTERING ORDER BY (start_time DESC); diff --git a/src/database/cassandra-handler/init-scripts/6__create_tests.cql b/src/database/cassandra-handler/init-scripts/6__create_tests.cql deleted file mode 100644 index fb45a58a6..000000000 --- a/src/database/cassandra-handler/init-scripts/6__create_tests.cql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS tests( -id uuid, -updated_at timestamp, -raw_data text, // The request as is that came from the client -artillery_json text, // The json that has been generated for the Artillery -revision_id uuid, -name text, -type text, -description text, -PRIMARY KEY (id, updated_at) -) -WITH compression = { 'sstable_compression' : 'LZ4Compressor' }; diff --git a/src/database/cassandra-handler/init-scripts/7__create_config_table.cql b/src/database/cassandra-handler/init-scripts/7__create_config_table.cql deleted file mode 100644 index dd311f31c..000000000 --- a/src/database/cassandra-handler/init-scripts/7__create_config_table.cql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE IF NOT EXISTS config( -key text, -value text, -PRIMARY KEY (key)); \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/8__dsl_table.cql b/src/database/cassandra-handler/init-scripts/8__dsl_table.cql deleted file mode 100644 index 888e157c3..000000000 --- a/src/database/cassandra-handler/init-scripts/8__dsl_table.cql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS dsl( -dsl_name text, -definition_name text, -artillery_json text, // The json that has been generated for the Artillery -PRIMARY KEY (dsl_name, definition_name) -) -WITH compression = { 'sstable_compression' : 'LZ4Compressor' }; \ No newline at end of file diff --git a/src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql b/src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql deleted file mode 100644 index 09bad07ef..000000000 --- a/src/database/cassandra-handler/init-scripts/9__add_proxy_and_debug_jobs_table_.cql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE jobs ADD proxy_url text; ---- -ALTER TABLE jobs ADD debug text; \ No newline at end of file diff --git a/src/database/database.js b/src/database/database.js index 6ed24fc74..27e96acf8 100644 --- a/src/database/database.js +++ b/src/database/database.js @@ -1,9 +1,8 @@ 'use strict'; -let databaseConfig = require('../config/databaseConfig'); -let cassandraConnector = require('./cassandra-handler/cassandra'); let sequelizeConnector = require('./sequlize-handler/sequlize'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; + module.exports.init = () => { return databaseConnector.init(); }; @@ -14,4 +13,4 @@ module.exports.ping = () => { module.exports.closeConnection = () => { return databaseConnector.closeConnection(); -}; \ No newline at end of file +}; diff --git a/src/env.js b/src/env.js index 810fa8d65..a3eb6f8f7 100644 --- a/src/env.js +++ b/src/env.js @@ -8,9 +8,7 @@ const BY_PLATFORM_MANDATORY_VARS = { }; const SUPPORTED_PLATFORMS = Object.keys(BY_PLATFORM_MANDATORY_VARS); -const SUPPORTED_CASSANDRA_STRATEGY = ['SimpleStrategy', 'NetworkTopologyStrategy']; const BY_DATABASE_MANDATORY_VARS = { - CASSANDRA: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], MYSQL: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], POSTGRES: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], MSSQL: ['DATABASE_NAME', 'DATABASE_ADDRESS', 'DATABASE_USERNAME', 'DATABASE_PASSWORD'], @@ -49,13 +47,6 @@ env.init = function () { log.error('Missing mandatory environment variables', missingFields); process.exit(1); } - - if (process.env.CASSANDRA_KEY_SPACE_STRATEGY && !SUPPORTED_CASSANDRA_STRATEGY.includes(process.env.CASSANDRA_KEY_SPACE_STRATEGY)) { - throw new Error('CASSANDRA_KEY_SPACE_STRATEGY not one of the supported values: ' + SUPPORTED_CASSANDRA_STRATEGY); - } - if (process.env.CASSANDRA_KEY_SPACE_STRATEGY === 'NetworkTopologyStrategy' && !process.env.CASSANDRA_LOCAL_DATA_CENTER) { - throw new Error('When using CASSANDRA_KEY_SPACE_STRATEGY: NetworkTopologyStrategy, CASSANDRA_LOCAL_DATA_CENTER is mandatory'); - } }; -module.exports = env; \ No newline at end of file +module.exports = env; diff --git a/src/files/models/database.js b/src/files/models/database.js index e70b7423b..6f146f56b 100644 --- a/src/files/models/database.js +++ b/src/files/models/database.js @@ -1,7 +1,5 @@ -const cassandraConnector = require('./database/cassandra/cassandraConnector'), - sequelizeConnector = require('./database/sequelize/sequelizeConnector'), - databaseConfig = require('../../config/databaseConfig'), - databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +const sequelizeConnector = require('./database/sequelize/sequelizeConnector'), + databaseConnector = sequelizeConnector; module.exports = { saveFile, diff --git a/src/files/models/database/cassandra/cassandraConnector.js b/src/files/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index 11e14c1ca..000000000 --- a/src/files/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,48 +0,0 @@ -let logger = require('../../../../common/logger'); -let cassandra = require('cassandra-driver'); -let client = {}; - -const INSERT_FILE = 'INSERT INTO files(id,name,file) values(?,?,?)'; -const GET_FILE_WITH_CONTENT = 'SELECT * FROM files WHERE id = ?'; -const GET_FILE_METADATA = 'SELECT id, name FROM files WHERE id = ?'; - -module.exports = { - init, - saveFile, - getFile -}; - -let queryOptions = { - consistency: cassandra.types.consistencies.localQuorum, - prepare: true -}; - -function init(cassandraClient) { - client = cassandraClient; -} - -async function executeQuery(query, params, queryOptions) { - try { - const result = await client.execute(query, params, queryOptions); - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return result; - } catch (err){ - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, err); - throw new Error('Error occurred in communication with cassandra'); - } -} - -async function saveFile(id, fileName, fileContent) { - let params = [id, fileName, fileContent]; - const result = await executeQuery(INSERT_FILE, params, queryOptions); - return result; -} - -async function getFile(id, isIncludeContent) { - const result = await executeQuery(isIncludeContent ? GET_FILE_WITH_CONTENT : GET_FILE_METADATA, [id], queryOptions); - return result.rows[0] ? result.rows[0] : undefined; -} diff --git a/src/jobs/models/database/cassandra/cassandraConnector.js b/src/jobs/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index 28984dc46..000000000 --- a/src/jobs/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,98 +0,0 @@ -let logger = require('../../../../common/logger'); -let databaseConfig = require('../../../../config/databaseConfig'); -let client; - -const INSERT_JOB = 'INSERT INTO jobs(id, test_id, arrival_rate, cron_expression, duration, emails, environment, ramp_to, webhooks, parallelism, max_virtual_users, notes, proxy_url, debug, enabled) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; -const GET_JOBS = 'SELECT * FROM jobs'; -const DELETE_JOB = 'DELETE FROM jobs WHERE id=?'; -const GET_JOB = 'SELECT * FROM jobs WHERE id=?'; -const GET_COLUMNS = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - -let columns; - -module.exports = { - init, - insertJob, - getJobs, - getJob, - deleteJob, - updateJob -}; - -let queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -async function init(cassandraClient) { - client = cassandraClient; -} - -function deleteJob(jobId) { - return executeQuery(DELETE_JOB, [jobId]); -} - -function getJobs() { - return executeQuery(GET_JOBS, []); -} - -function getJob(jobId) { - return executeQuery(GET_JOB, [jobId]); -} - -function insertJob(jobId, jobInfo) { - let params = [jobId, jobInfo.test_id, jobInfo.arrival_rate, jobInfo.cron_expression, jobInfo.duration, jobInfo.emails, jobInfo.environment, jobInfo.ramp_to, jobInfo.webhooks, jobInfo.parallelism, jobInfo.max_virtual_users, jobInfo.notes, jobInfo.proxy_url, jobInfo.debug, jobInfo.enabled]; - return executeQuery(INSERT_JOB, params, queryOptions); -} - -async function updateJob(jobId, jobInfo) { - let params = []; - let updateQuery = 'UPDATE jobs SET '; - - if (!columns) { - columns = await getColumns(); - } - - let error; - - Object.keys(jobInfo).forEach(function (key) { - if (!(columns.indexOf(key) <= -1)) { - updateQuery += key + '=?, '; - params.push(jobInfo[key]); - } - }); - - if (error) { - return Promise.reject(error); - } - - if (params.length > 0) { - updateQuery = updateQuery.substring(0, updateQuery.length - 2); - updateQuery += ' WHERE id=? IF EXISTS'; - params.push(jobId); - return executeQuery(updateQuery, params); - } -} - -async function getColumns() { - let getColumnsResponse = await executeQuery(GET_COLUMNS, [databaseConfig.name]); - let columns = getColumnsResponse.map(row => { - return row.column_name; - }); - columns = columns.filter(column => !(columns === 'job_id' || column === 'id')); - return columns; -} - -function executeQuery(query, params, queryOptions) { - return client.execute(query, params, { prepare: true }, queryOptions).then((result) => { - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - }).catch((exception) => { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -} diff --git a/src/jobs/models/database/databaseConnector.js b/src/jobs/models/database/databaseConnector.js index 913ad8670..e27f8fa8b 100644 --- a/src/jobs/models/database/databaseConnector.js +++ b/src/jobs/models/database/databaseConnector.js @@ -1,9 +1,7 @@ 'use strict'; -let databaseConfig = require('../../../config/databaseConfig'); -let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; module.exports = { init, @@ -41,4 +39,4 @@ async function init() { function closeConnection() { return databaseConnector.closeConnection(); -} \ No newline at end of file +} diff --git a/src/processors/models/database/cassandra/cassandraConnector.js b/src/processors/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index c06f49cf1..000000000 --- a/src/processors/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,114 +0,0 @@ -let logger = require('../../../../common/logger'); -let databaseConfig = require('../../../../config/databaseConfig'); -let _ = require('lodash'); -let client; - -const JAVASCRIPT = 'javascript'; -const INSERT_PROCESSOR = 'INSERT INTO processors(id, name, description, javascript, exported_functions, created_at, updated_at) values(?,?,?,?,?,?,?)'; -const GET_ALL_PROCESSORS = 'SELECT * FROM processors'; -const GET_ALL_PROCESSORS_NO_JAVASCRIPT = 'SELECT id, name, description, created_at, updated_at, exported_functions FROM processors'; - -const GET_PROCESSOR_BY_ID = 'SELECT * FROM processors WHERE id=?'; -const DELETE_PROCESSOR = 'DELETE FROM processors WHERE id=?'; -const UPDATE_PROCESSOR = 'UPDATE processors SET name=?, description=?, javascript=?, exported_functions=?, updated_at=? WHERE id=? AND created_at=? IF EXISTS'; - -const INSERT_PROCESSOR_MAPPING = 'INSERT INTO processors_mapping(name, id) VALUES(?, ?)'; -const DELETE_PROCESSOR_MAPPING = 'DELETE FROM processors_mapping WHERE name=?'; -const GET_PROCESSOR_MAPPING = 'SELECT * FROM processors_mapping WHERE name=?'; - -module.exports = { - init, - insertProcessor, - getAllProcessors, - getProcessorById, - getProcessorByName, - deleteProcessor, - updateProcessor, - _queries: { - INSERT_PROCESSOR_MAPPING, - DELETE_PROCESSOR_MAPPING, - GET_PROCESSOR_MAPPING, - INSERT_PROCESSOR, - GET_ALL_PROCESSORS, - GET_PROCESSOR_BY_ID, - DELETE_PROCESSOR, - UPDATE_PROCESSOR - } -}; - -let queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -async function init(cassandraClient) { - client = cassandraClient; -} - -async function getAllProcessors(from, limit, exclude) { - let query = GET_ALL_PROCESSORS; - if (exclude && (exclude === JAVASCRIPT || exclude.includes(JAVASCRIPT))) { - query = GET_ALL_PROCESSORS_NO_JAVASCRIPT; - } - const resultRows = await executeQuery(query, [], queryOptions); - return _(resultRows).slice(from).take(limit).value(); -} - -async function getProcessorByName(processorName) { - const [processorMapping] = await executeQuery(GET_PROCESSOR_MAPPING, [processorName], queryOptions); - if (processorMapping) { - return getProcessorById(processorMapping.id); - } -} - -async function getProcessorById(processorId) { - const processor = await executeQuery(GET_PROCESSOR_BY_ID, [processorId], queryOptions); - return processor[0]; -} - -async function deleteProcessor(processorId) { - let params = [processorId]; - let processor = await getProcessorById(processorId); - if (processor) { - let mappingParams = [processor.name]; - return Promise.all([ - executeQuery(DELETE_PROCESSOR, params, queryOptions), - executeQuery(DELETE_PROCESSOR_MAPPING, mappingParams, queryOptions) - ]); - } -} - -async function insertProcessor(processorId, processorInfo) { - let params = [processorId, processorInfo.name, processorInfo.description, processorInfo.javascript, processorInfo.exported_functions, Date.now(), Date.now()]; - let mappingParams = [processorInfo.name, processorId]; - const [processor] = await Promise.all([ - executeQuery(INSERT_PROCESSOR, params, queryOptions), - executeQuery(INSERT_PROCESSOR_MAPPING, mappingParams, queryOptions) - ]); - return processor; -} - -async function updateProcessor(processorId, updatedProcessor) { - const { name, description, javascript, exported_functions, created_at: createdAt } = updatedProcessor; - const processor = await getProcessorById(processorId); - const params = [ name, description, javascript, exported_functions, Date.now(), processorId, createdAt.getTime() ]; - return Promise.all([ - executeQuery(UPDATE_PROCESSOR, params, queryOptions), - executeQuery(INSERT_PROCESSOR_MAPPING, [updatedProcessor.name, processorId]), - executeQuery(DELETE_PROCESSOR_MAPPING, [processor.name]) - ]); -} - -function executeQuery(query, params, queryOptions) { - return client.execute(query, params, { prepare: true }, queryOptions).then((result) => { - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - }).catch((exception) => { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -} diff --git a/src/processors/models/database/databaseConnector.js b/src/processors/models/database/databaseConnector.js index d9bccd187..68e703217 100644 --- a/src/processors/models/database/databaseConnector.js +++ b/src/processors/models/database/databaseConnector.js @@ -1,7 +1,5 @@ -let databaseConfig = require('../../../config/databaseConfig'); -let cassandraConnector = require('./cassandra/cassandraConnector'); let sequelizeConnector = require('./sequelize/sequelizeConnector'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; module.exports = { init, getAllProcessors, diff --git a/src/reports/models/database/cassandra/cassandraConnector.js b/src/reports/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index b3238d698..000000000 --- a/src/reports/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,244 +0,0 @@ -'use strict'; -const databaseConfig = require('../../../../config/databaseConfig'), - dateUtil = require('../../../utils/dateUtil'), - _ = require('lodash'), - constants = require('../../../utils/constants'); -const logger = require('../../../../common/logger'); -let client; -const isRowAppliedField = '[applied]'; -const INSERT_REPORT_SUMMARY = 'INSERT INTO reports_summary(test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; -const INSERT_LAST_REPORT_SUMMARY = 'INSERT INTO last_reports(start_time_year,start_time_month,test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; -const UPDATE_REPORT_BENCHMARK = 'UPDATE reports_summary SET score=?, benchmark_weights_data=? WHERE test_id=? AND report_id=?'; -const DELETE_REPORT_SUMMARY = 'DELETE from reports_summary WHERE test_id=? AND report_id=?'; -const GET_REPORT_SUMMARY = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; -const GET_REPORTS_SUMMARIES = 'SELECT * FROM reports_summary WHERE test_id=?'; -const GET_LAST_SUMMARIES = 'SELECT * FROM last_reports WHERE start_time_year=? AND start_time_month=? LIMIT ?'; -const INSERT_REPORT_STATS = 'INSERT INTO reports_stats(runner_id, test_id, report_id, stats_id, stats_time, phase_index, phase_status, data) values(?,?,?,?,?,?,?,?)'; -const GET_REPORT_STATS = 'SELECT * FROM reports_stats WHERE test_id=? AND report_id=?'; -const SUBSCRIBE_RUNNER = 'INSERT INTO report_subscribers(test_id, report_id, runner_id, phase_status) values(?,?,?,?)'; -const UPDATE_SUBSCRIBER_WITH_STATS = 'UPDATE report_subscribers SET phase_status=?, last_stats=? WHERE test_id=? AND report_id=? AND runner_id=?'; -const UPDATE_SUBSCRIBER = 'UPDATE report_subscribers SET phase_status=? WHERE test_id=? AND report_id=? AND runner_id=?'; -const GET_REPORT_SUBSCRIBERS = 'SELECT * FROM report_subscribers WHERE test_id=? AND report_id=?'; - -module.exports = { - init, - insertReport, - updateReport, - deleteReport, - getReport, - getReports, - getLastReports, - insertStats, - getStats, - subscribeRunner, - updateSubscriberWithStats, - updateSubscriber, - updateReportBenchmark -}; - -let queryOptions = { - consistency: databaseConfig.cassandraConsistency, - prepare: true -}; - -async function init(cassandraClient) { - client = cassandraClient; -} - -async function insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) { - let params; - const testNotes = notes || ''; - params = [testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, testNotes, lastUpdatedAt]; - const result = await executeQuery(INSERT_REPORT_SUMMARY, params, queryOptions); - if (result[0][isRowAppliedField]) { - insertLastReportAsync(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt); - } - return result; -} - -function insertLastReportAsync(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) { - let params; - const testNotes = notes || ''; - const startTimeDate = new Date(startTime); - const startTimeYear = startTimeDate.getFullYear(); - const startTimeMonth = startTimeDate.getMonth() + 1; - params = [startTimeYear, startTimeMonth, testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, testNotes, lastUpdatedAt]; - return executeQuery(INSERT_LAST_REPORT_SUMMARY, params, queryOptions) - .catch(err => logger.error(`Cassandra insertLastReportAsync failed \n ${JSON.stringify({ - INSERT_LAST_REPORT_SUMMARY, - params, - queryOptions - })}`, err)); -} - -async function updateReport(testId, reportId, reportData) { - const UPDATE_REPORT_SUMMARY = 'UPDATE reports_summary'; - const where = 'WHERE test_id=? AND report_id=?'; - const queryData = buildUpdateQuery(UPDATE_REPORT_SUMMARY, reportData, where, [testId, reportId]); - - updateLastReportAsync(testId, reportId, reportData); - return executeQuery(queryData.query, queryData.params, queryOptions); -} - -async function deleteReport(testId, reportId) { - const reportToDelete = await executeQuery(GET_REPORT_SUMMARY, [testId, reportId], queryOptions); - - const startTime = reportToDelete[0].start_time; - const startTimeDate = new Date(startTime); - const startTimeYear = startTimeDate.getFullYear(); - const startTimeMonth = startTimeDate.getMonth() + 1; - const where = 'WHERE start_time_year=? AND start_time_month=? AND start_time=? AND test_id=? AND report_id=?'; - const whereParams = [startTimeYear, startTimeMonth, startTime, testId, reportId]; - const deleteLastReport = 'DELETE from last_reports'; - - await executeQuery(`${deleteLastReport} ${where}`, whereParams, queryOptions); - await executeQuery(DELETE_REPORT_SUMMARY, [testId, reportId], queryOptions); -} - -function buildUpdateQuery(baseQuery, values, where, whereDataArray) { - const entriesValues = Object.entries(values); - const params = entriesValues.map((entry) => entry[1]).concat(whereDataArray); - const setStatement = `SET ${entriesValues.map((entry) => `${entry[0]}=?`).join(', ')}`; - const query = `${baseQuery} ${setStatement} ${where}`; - - return { - query, - params - }; -} - -async function updateReportBenchmark(testId, reportId, score, benchmarkData) { - const reportData = { - score, - benchmark_weights_data: benchmarkData - }; - updateLastReportAsync(testId, reportId, reportData); - const res = await executeQuery(UPDATE_REPORT_BENCHMARK, [score, benchmarkData, testId, reportId], { prepare: true }); - return res; -} - -async function updateLastReportAsync(testId, reportId, reportData) { - let queryData = {}; - try { - const reportToUpdate = await executeQuery(GET_REPORT_SUMMARY, [testId, reportId], queryOptions); - const startTime = reportToUpdate[0].start_time; - const startTimeDate = new Date(startTime); - const startTimeYear = startTimeDate.getFullYear(); - const startTimeMonth = startTimeDate.getMonth() + 1; - - const where = 'WHERE start_time_year=? AND start_time_month=? AND start_time=? AND test_id=? AND report_id=?'; - const whereParams = [startTimeYear, startTimeMonth, startTime, testId, reportId]; - const UPDATE_LAST_REPORT_SUMMARY = 'UPDATE last_reports'; - - queryData = buildUpdateQuery(UPDATE_LAST_REPORT_SUMMARY, reportData, where, whereParams); - - await executeQuery(queryData.query, queryData.params, queryOptions); - } catch (err) { - logger.error(`Cassandra updateLastReportAsync failed \n ${JSON.stringify({ - query: queryData.query, - params: queryData.params, - queryOptions - })}`, err); - } -} - -function getReport(testId, reportId) { - let params; - params = [testId, reportId]; - return getReportsWIthSubscribers(GET_REPORT_SUMMARY, params, queryOptions); -} - -function getReports(testId) { - let params; - params = [testId]; - return getReportsWIthSubscribers(GET_REPORTS_SUMMARIES, params, queryOptions); -} - -function getLastReports(limit) { - return getLastReportsWIthSubscribers(limit); -} - -function insertStats(runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data) { - let params; - params = [runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data]; - return executeQuery(INSERT_REPORT_STATS, params, queryOptions); -} - -function getStats(testId, reportId) { - let params; - params = [testId, reportId]; - return executeQuery(GET_REPORT_STATS, params, queryOptions); -} - -function subscribeRunner(testId, reportId, runnerId, phaseStatus) { - let params; - params = [testId, reportId, runnerId, phaseStatus]; - return executeQuery(SUBSCRIBE_RUNNER, params, queryOptions); -} - -async function updateSubscriberWithStats(testId, reportId, runnerId, phaseStatus, lastStats) { - let params; - params = [phaseStatus, lastStats, testId, reportId, runnerId]; - return executeQuery(UPDATE_SUBSCRIBER_WITH_STATS, params, queryOptions); -} - -async function updateSubscriber(testId, reportId, runnerId, phaseStatus) { - let params; - params = [phaseStatus, testId, reportId, runnerId]; - return executeQuery(UPDATE_SUBSCRIBER, params, queryOptions); -} - -function getReportSubscribers(testId, reportId) { - let params; - params = [testId, reportId]; - return executeQuery(GET_REPORT_SUBSCRIBERS, params, queryOptions); -} - -function executeQuery(query, params, queryOptions) { - return client.execute(query, params, queryOptions).then((result) => { - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return Promise.resolve(result.rows ? result.rows : []); - }).catch((exception) => { - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, exception); - return Promise.reject(new Error('Error occurred in communication with cassandra')); - }); -} - -async function getReportsWIthSubscribers(query, params, queryOptions) { - const reports = await executeQuery(query, params, queryOptions); - let reportsWithSubscribers = joinReportsWIthSubscribers(reports); - return reportsWithSubscribers; -} - -async function getLastReportsWIthSubscribers(limit) { - let lastReportsPromise = []; - for (let i = 0; i < constants.MAX_MONTH_OF_LAST_REPORTS; i++) { - const date = dateUtil.dateXMonthAgo(i); - lastReportsPromise.push(executeQuery(GET_LAST_SUMMARIES, [date.year, date.month, limit], queryOptions)); - } - const reportsResult = await Promise.all(lastReportsPromise); - const allReports = _(reportsResult).flatMap(value => value).value().slice(0, limit); - let reportsWIthSubscribers = joinReportsWIthSubscribers(allReports); - return reportsWIthSubscribers; -} - -async function joinReportsWIthSubscribers(reports) { - let subscribers, report; - for (let reportIndex = 0; reportIndex < reports.length; reportIndex++) { - report = reports[reportIndex]; - subscribers = await getReportSubscribers(report.test_id, report.report_id); - subscribers = subscribers.map((subscriber) => { - return { - 'runner_id': subscriber.runner_id, - 'phase_status': subscriber.phase_status, - 'last_stats': JSON.parse(subscriber.last_stats) - }; - }); - report.subscribers = subscribers; - } - return reports; -} diff --git a/src/reports/models/databaseConnector.js b/src/reports/models/databaseConnector.js index 4d20dcf2c..fe8dad43e 100644 --- a/src/reports/models/databaseConnector.js +++ b/src/reports/models/databaseConnector.js @@ -1,9 +1,7 @@ 'use strict'; -let databaseConfig = require('../../config/databaseConfig'); -let cassandraConnector = require('./database/cassandra/cassandraConnector'); let sequelizeConnector = require('./database/sequelize/sequelizeConnector'); -let databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +let databaseConnector = sequelizeConnector; module.exports = { insertReport, diff --git a/src/tests/models/database.js b/src/tests/models/database.js index 1d4c85f61..8d7802e04 100644 --- a/src/tests/models/database.js +++ b/src/tests/models/database.js @@ -1,7 +1,5 @@ -const cassandraConnector = require('./database/cassandra/cassandraConnector'), - sequelizeConnector = require('./database/sequelize/sequelizeConnector'), - databaseConfig = require('../../config/databaseConfig'), - databaseConnector = databaseConfig.type.toLowerCase() === 'cassandra' ? cassandraConnector : sequelizeConnector; +const sequelizeConnector = require('./database/sequelize/sequelizeConnector'), + databaseConnector = sequelizeConnector; module.exports = { insertTest, diff --git a/src/tests/models/database/cassandra/cassandraConnector.js b/src/tests/models/database/cassandra/cassandraConnector.js deleted file mode 100644 index 9deac8520..000000000 --- a/src/tests/models/database/cassandra/cassandraConnector.js +++ /dev/null @@ -1,153 +0,0 @@ -let logger = require('../../../../common/logger'); -let cassandra = require('cassandra-driver'); -let client = {}; -let uuid = require('cassandra-driver').types.Uuid; -const sanitizeHelper = require('../../../helpers/sanitizeHelper'); - -const INSERT_TEST_DETAILS = 'INSERT INTO tests(id, name, description, type, updated_at, raw_data, artillery_json, revision_id, file_id, csv_file_id, processor_id) values(?,?,?,?,?,?,?,?,?,?,?)'; -const GET_TEST = 'SELECT * FROM tests WHERE id = ? ORDER BY updated_at DESC limit 1'; -const GET_TEST_REVISIONS = 'SELECT * FROM tests WHERE id = ?'; -const GET_TESTS = 'SELECT * FROM tests'; -const DELETE_TEST = 'DELETE FROM tests WHERE id=?'; -const INSERT_BENCHMARK_DATA_TEST = 'INSERT INTO benchmarks(test_id,data) values(?,?)'; -const GET_BENCHMARK_DATA_TEST = 'SELECT * FROM benchmarks WHERE test_id=?'; - -const INSERT_DSL_DEFINITION_IF_NOT_EXIST = 'INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS'; -const UPDATE_DSL_DEFINITION = 'UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'; -const DELETE_DSL_DEFINITION = 'DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'; -const GET_DSL_DEFINITION = 'SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1'; -const GET_DSL_DEFINITIONS = 'SELECT * FROM dsl WHERE dsl_name = ?'; - -module.exports = { - init, - insertTest, - getAllTestRevisions, - getTest, - getTests, - deleteTest, - insertDslDefinition, - getDslDefinition, - getDslDefinitions, - updateDslDefinition, - deleteDefinition, - insertTestBenchmark, - getTestBenchmark -}; - -let queryOptions = { - consistency: cassandra.types.consistencies.localQuorum, - prepare: true -}; - -function init(cassandraClient) { - client = cassandraClient; -} - -async function getTest(id) { - id = uuid.fromString(id); - const result = await executeQuery(GET_TEST, [id], queryOptions); - const sanitizedResult = sanitizeTestResult(result.rows)[0]; - return sanitizedResult; -} - -async function getTests() { - const result = await executeQuery(GET_TESTS, [], queryOptions); - const sanitizedResult = sanitizeTestResult(result.rows); - return sanitizedResult; -} - -async function deleteTest(testId){ - const result = await executeQuery(DELETE_TEST, [testId]); - return result; -} - -async function insertTestBenchmark(testId, benchmarkData) { - testId = uuid.fromString(testId); - const result = await executeQuery(INSERT_BENCHMARK_DATA_TEST, [testId, benchmarkData]); - return result; -} - -async function getTestBenchmark(testId) { - const result = await executeQuery(GET_BENCHMARK_DATA_TEST, [testId]); - return result.rows.length > 0 ? result.rows[0].data : undefined; -} - -async function getAllTestRevisions(id) { - id = uuid.fromString(id); - const result = await executeQuery(GET_TEST_REVISIONS, [id], queryOptions); - const sanitizedResult = await sanitizeTestResult(result.rows); - return sanitizedResult; -} - -async function insertTest(testInfo, testJson, id, revisionId, processorFileId) { - let params; - params = [id, testInfo.name, testInfo.description, testInfo.type, Date.now(), JSON.stringify(testInfo), JSON.stringify(testJson), revisionId, processorFileId, testInfo.csv_file_id, testInfo.processor_id]; - const result = await executeQuery(INSERT_TEST_DETAILS, params, queryOptions); - return result; -} - -async function getDslDefinition(dslName, definitionName) { - const params = [dslName, definitionName]; - const result = await executeQuery(GET_DSL_DEFINITION, params, queryOptions); - const sanitizedResult = sanitizeDslResult(result.rows); - return sanitizedResult[0]; -} -async function getDslDefinitions(dslName) { - const params = [dslName]; - const result = await executeQuery(GET_DSL_DEFINITIONS, params, queryOptions); - const sanitizedResult = sanitizeDslResult(result.rows); - return sanitizedResult; -} - -async function insertDslDefinition(dslName, definitionName, data) { - const params = [dslName, definitionName, JSON.stringify(data)]; - const result = await executeQuery(INSERT_DSL_DEFINITION_IF_NOT_EXIST, params, queryOptions); - return result.rows[0]['[applied]']; -} - -async function updateDslDefinition(dslName, definitionName, data) { - const params = [JSON.stringify(data), dslName, definitionName]; - const result = await executeQuery(UPDATE_DSL_DEFINITION, params, queryOptions); - return result.rows[0]['[applied]']; -} -async function deleteDefinition(dslName, definitionName) { - const params = [dslName, definitionName]; - const result = await executeQuery(DELETE_DSL_DEFINITION, params, queryOptions); - return result.rows[0]['[applied]']; -} - -async function executeQuery(query, params, queryOptions) { - try { - const result = await client.execute(query, params, queryOptions); - logger.trace('Query result', { - query: query, - params: params, - rows_returned: result.rowLength - }); - return result; - } catch (err){ - logger.error(`Cassandra query failed \n ${JSON.stringify({ query, params, queryOptions })}`, err); - throw new Error('Error occurred in communication with cassandra'); - } -} - -function sanitizeTestResult(data) { - const result = data.map(function (row) { - const dslDataObject = sanitizeHelper.extractDslRootData(row.raw_data); - row.artillery_json = row.artillery_json ? JSON.parse(row.artillery_json) : undefined; - row.file_id = row.file_id || undefined; - row.csv_file_id = row.csv_file_id || undefined; - row.processor_id = row.processor_id || undefined; - delete row.raw_data; - return Object.assign(row, dslDataObject); - }); - return result; -} - -function sanitizeDslResult(data) { - const result = data.map(function (row) { - row.artillery_json = JSON.parse(row.artillery_json); - return row; - }); - return result; -} diff --git a/src/tests/models/dsl.js b/src/tests/models/dsl.js index 24c2c91f3..9b3b9f045 100644 --- a/src/tests/models/dsl.js +++ b/src/tests/models/dsl.js @@ -38,7 +38,7 @@ async function createDefinition(dslName, body) { utils.addDefaultsToStep(body.request); const result = await database.insertDslDefinition(dslName, body.name, body.request); if (result){ - logger.info(body, 'Definition created successfully and saved to Cassandra'); + logger.info(body, 'Definition created successfully and saved to DB'); return { name: body.name, request: body.request @@ -54,7 +54,7 @@ async function updateDefinition(dslName, definitionName, body) { utils.addDefaultsToStep(body.request); const result = await database.updateDslDefinition(dslName, definitionName, body.request); if (result){ - logger.info(body, 'Definition updated successfully and saved to Cassandra'); + logger.info(body, 'Definition updated successfully and saved to DB'); return { name: body.name, request: body.request @@ -68,7 +68,7 @@ async function updateDefinition(dslName, definitionName, body) { async function deleteDefinition(dslName, definitionName, body) { const result = await database.deleteDefinition(dslName, definitionName); if (result){ - logger.info(body, 'Definition deleted successfully and saved to Cassandra'); + logger.info(body, 'Definition deleted successfully and saved to DB'); } else { const error = new Error(ERROR_MESSAGES.NOT_FOUND); error.statusCode = 404; diff --git a/tests/configurations/cassandraConfiguration.sh b/tests/configurations/cassandraConfiguration.sh deleted file mode 100755 index 7668de4ec..000000000 --- a/tests/configurations/cassandraConfiguration.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -e - -export DATABASE_ADDRESS=$RUNNER_IP:9042 -export DATABASE_NAME=cassandra_keyspace -export DATABASE_USERNAME=root -export DATABASE_PASSWORD=password -export REPLICATION_FACTOR=1 -export DATABASE_TYPE=CASSANDRA \ No newline at end of file diff --git a/tests/configurations/dockerRun.sh b/tests/configurations/dockerRun.sh index 5b01362b3..4bef518a3 100755 --- a/tests/configurations/dockerRun.sh +++ b/tests/configurations/dockerRun.sh @@ -72,26 +72,6 @@ function postgres() { echo "$APP is ready" } -function cassandra() { - IMAGE_NAME=cassandra:3.11 - APP=cassandra - stop $APP - COMMAND="docker run \ - -d \ - --name $APP \ - -p 9042:9042 \ - $IMAGE_NAME" - echo -e "Starting $APP\n"${COMMAND/\s+/ } - $COMMAND - COMMAND_EXIT_CODE=$? - if [ ${COMMAND_EXIT_CODE} != 0 ]; then - printf "Error when executing: '${APP}'\n" - exit ${COMMAND_EXIT_CODE} - fi - waitForApp $APP "Created default superuser role 'cassandra'" - echo "$APP is ready" -} - function mailhog() { IMAGE_NAME=mailhog/mailhog APP=mailhog @@ -130,9 +110,6 @@ for option in ${@}; do postgres) postgres ;; - cassandra) - cassandra - ;; reporter) reporter ;; @@ -146,7 +123,7 @@ for option in ${@}; do stop ;; *) - echo "Usage: ./dockerRun.sh " + echo "Usage: ./dockerRun.sh " ;; esac done diff --git a/tests/integration-tests/runLocal.sh b/tests/integration-tests/runLocal.sh index cc9e1384b..9865edd15 100755 --- a/tests/integration-tests/runLocal.sh +++ b/tests/integration-tests/runLocal.sh @@ -1,9 +1,6 @@ #!/bin/bash -e -LOCAL_TEST=true DATABASE_TYPE=cassandra JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=mysql JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=sqlite JOB_PLATFORM=kubernetes ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=postgres JOB_PLATFORM=metronome ./tests/integration-tests/run.sh LOCAL_TEST=true DATABASE_TYPE=sqlite JOB_PLATFORM=docker ./tests/integration-tests/run.sh - - diff --git a/tests/unit-tests/cassandra-handler/cassandraHandler-test.js b/tests/unit-tests/cassandra-handler/cassandraHandler-test.js deleted file mode 100644 index a1cc9b12b..000000000 --- a/tests/unit-tests/cassandra-handler/cassandraHandler-test.js +++ /dev/null @@ -1,509 +0,0 @@ -// 'use strict'; -// -// let sinon = require('sinon'); -// let rewire = require('rewire'); -// let chai = require('chai'); -// let expect = chai.expect; -// let chaiSinon = require('chai-sinon'); -// chai.use(chaiSinon); -// let logger = require('../../../src/helpers/logger'); -// let cassandra = require('cassandra-driver'); -// let cassandraConfig = require('../../../src/config/databaseConfig'); -// let cassandraHandler = rewire('../../../src/cassandra-handler/cassandraHandler'); -// -// describe('cassandra handler tests', function () { -// let sandbox; -// cassandraConfig.address = 'localhost:9042'; -// cassandraConfig.username = 'username'; -// cassandraConfig.password = 'password'; -// cassandraConfig.name = 'predator'; -// -// before(function () { -// sandbox = sinon.sandbox.create(); -// }); -// -// afterEach(function () { -// sandbox.restore(); -// }); -// -// describe('cassandra ping tests', function () { -// let clientExecuteStub; -// -// beforeEach(function () { -// sandbox.reset(); -// let client = { -// execute: sinon.stub() -// }; -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// clientExecuteStub = client.execute; -// }); -// -// it('ping is ok, cassandra is up', function (done) { -// clientExecuteStub.resolves({rows: [{}]}); -// -// cassandraHandler.initArgs(); -// cassandraHandler.initCassandraConnection() -// .then(function () { -// return cassandraHandler.ping('keyspace'); -// }).then(function (result) { -// expect(result).to.be.true; -// done(); -// }).catch(function (error) { -// done(error); -// }); -// }); -// -// it('ping rejects as no schema, cassandra is down', function (done) { -// clientExecuteStub.resolves({rows: []}); -// -// cassandraHandler.initArgs(); -// cassandraHandler.initCassandraConnection() -// .then(function () { -// return cassandraHandler.ping('keyspace'); -// }).then(function () { -// done('Expecting test to fail'); -// }).catch(function (error) { -// expect(error.message).to.eql('Key space doesn\'t found'); -// done(); -// }); -// }); -// -// it('cassandra rejects with error, cassandra is down', function (done) { -// clientExecuteStub.rejects({message: 'failure'}); -// -// cassandraHandler.initArgs(); -// cassandraHandler.initCassandraConnection() -// .then(function () { -// return cassandraHandler.ping('keyspace'); -// }).then(function () { -// done('Expecting test to fail'); -// }).catch(function (error) { -// expect(error.message).to.eql('failure'); -// done(); -// }); -// }); -// }); -// -// describe('initializeCassandraEnvironment tests', function () { -// beforeEach(function () { -// cassandraConfig.address = 'localhost:9042'; -// cassandraConfig.username = 'cassandra'; -// cassandraConfig.password = 'cassandra'; -// let cassandraHandlerLogContext = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': cassandraConfig.name, -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json' -// }; -// cassandraHandler.__set__('cassandraHandlerLogContext', cassandraHandlerLogContext); -// }); -// -// afterEach(function () { -// sandbox.restore(); -// }); -// -// describe('createKeySpaceIfNeeded rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = { -// innerErrors: 'client execute error' -// }; -// clientExecuteStub.yields(error); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(null); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'create_key_space_query_err': { -// 'innerErrors': 'client execute error' -// }, -// 'create_key_space_query_inner_err': 'client execute error', -// 'initialize_cassandra_environment_error': { -// 'innerErrors': 'client execute error' -// } -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.not.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.not.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.not.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: could not create keyspace')).to.be.true; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: error occurred while trying to init cassandra credentials')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }).catch(function (error) { -// done(error); -// }); -// }); -// }); -// describe('closeCassandraConnection rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = { -// innerErrors: 'client execute error' -// }; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(error); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(null); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'client_shutdown_err': { -// 'innerErrors': 'client execute error' -// }, -// 'initialize_cassandra_environment_error': { -// 'innerErrors': 'client execute error' -// } -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.not.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.not.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: failed to close Cassandra connection.')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// describe('createConfigTemplateFile rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = 'error_message'; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(error); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'remove_config_template_file_err': 'error_message', -// 'initialize_cassandra_environment_error': 'error_message' -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.not.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: could not write to cassandra init file')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// describe('runCassandraScripts rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = 'error_message'; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(undefined); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(null); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(error, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'run_cassandra_scripts_err': 'error_message', -// 'run_cassandra_scripts_stderr': null, -// 'initialize_cassandra_environment_error': 'error_message' -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.been.calledOnce; -// expect(fsRemoveStub).to.have.not.been.calledOnce; -// expect(cmdStub).to.have.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: failed running cassandra migration scripts')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// describe('removeConfigFile rejects', function () { -// it('Should exit the process and log the error', function (done) { -// let stubAuthProvider = sandbox.stub(cassandra.auth, 'PlainTextAuthProvider'); -// stubAuthProvider.returns({ -// 'test': 'test' -// }); -// -// let client = { -// execute: sinon.stub(), -// shutdown: sinon.stub() -// }; -// -// let clientExecuteStub = client.execute; -// let error = 'error_message'; -// clientExecuteStub.yields(undefined); -// -// let clientShutdownStub = client.shutdown; -// clientShutdownStub.yields(undefined); -// -// let stubClient = sandbox.stub(cassandra, 'Client'); -// stubClient.returns(client); -// -// let fs = { -// writeFile: sinon.stub(), -// remove: sinon.stub() -// }; -// -// let fsStub = fs.writeFile; -// cassandraHandler.__set__('fs', fs); -// fsStub.yields(undefined); -// -// let fsRemoveStub = fs.remove; -// fsRemoveStub.yields(error); -// -// let cmd = { -// get: sinon.stub() -// }; -// -// let cmdStub = cmd.get; -// cassandraHandler.__set__('cmd', cmd); -// cmdStub.yields(null, null, null); -// -// let stubErrorLogger = sinon.stub(logger, 'error'); -// -// let processStub = { -// exit: sinon.stub() -// }; -// -// let exitStub = processStub.exit; -// exitStub.resolves(); -// cassandraHandler.__set__('process', processStub); -// -// let initializeCassandraEnvironmentError = { -// 'x-zooz-request-id': 'service-startup', -// 'key_space_name': 'predator', -// 'init_file_name_template': 'cassandra_config_template.json', -// 'init_file_name': 'cassandra_config.json', -// 'remove_config_file_err': 'error_message', -// 'initialize_cassandra_environment_error': 'error_message' -// }; -// -// cassandraHandler.initializeCassandraEnvironment().then(function () { -// expect(stubAuthProvider.calledWith('cassandra', 'cassandra')).equal(true); -// expect(stubAuthProvider).to.have.been.calledOnce; -// expect(stubClient).to.have.been.calledOnce; -// expect(fsStub).to.have.been.calledOnce; -// expect(fsRemoveStub).to.have.been.calledOnce; -// expect(cmdStub).to.have.been.calledOnce; -// expect(clientExecuteStub).to.have.been.calledOnce; -// expect(clientShutdownStub).to.have.been.calledOnce; -// expect(stubErrorLogger.calledWith(initializeCassandraEnvironmentError, 'Cassandra handler: could not remove cassandra migration configManager file')).to.be.true; -// expect(exitStub).to.have.been.calledOnce; -// stubErrorLogger.restore(); -// done(); -// }); -// }); -// }); -// }); -// }); diff --git a/tests/unit-tests/configManager/cassandra/cassandra-test.js b/tests/unit-tests/configManager/cassandra/cassandra-test.js deleted file mode 100644 index 94b2fe3a1..000000000 --- a/tests/unit-tests/configManager/cassandra/cassandra-test.js +++ /dev/null @@ -1,128 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let should = require('should'); -let cassandraClient = rewire('../../../../src/configManager/models/database/cassandra/cassandraConnector'); - -describe('Cassandra client tests', function() { - let sandbox; - let clientBatchStub; - let clientExecuteStub; - let revert; - - before(() => { - sandbox = sinon.sandbox.create(); - clientBatchStub = sandbox.stub(driver.Client.prototype, 'batch'); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { batch: clientBatchStub, execute: clientExecuteStub }); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('Upsert new config record', () => { - it('should succeed simple update', async () => { - clientBatchStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO config(key, value) values(?,?)'; - await cassandraClient.updateConfig({ key: 'test_key' }); - - clientBatchStub.getCall(0).args[0][0].query.should.eql(query); - clientBatchStub.getCall(0).args[0][0].params[0].should.eql('key'); - clientBatchStub.getCall(0).args[0][0].params[1].should.eql('test_key'); - }); - }); - - describe('Upsert new config record object as value', () => { - it('should succeed object value update', async () => { - clientBatchStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO config(key, value) values(?,?)'; - let objectToSave = { test_json: 'json_value' }; - await cassandraClient.updateConfig({ key: objectToSave }); - - clientBatchStub.getCall(0).args[0][0].query.should.eql(query); - clientBatchStub.getCall(0).args[0][0].params[0].should.eql('key'); - clientBatchStub.getCall(0).args[0][0].params[1].should.eql(JSON.stringify(objectToSave)); - }); - }); - - describe('Upsert new config multiple records object and strings as value', () => { - it('should succeed object value update', async () => { - clientBatchStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO config(key, value) values(?,?)'; - let objectToSave = { object_key: 'test_key' }; - await cassandraClient.updateConfig({ stringValue: 'test_string', objectValue: objectToSave }); - - clientBatchStub.getCall(0).args[0][0].query.should.eql(query); - clientBatchStub.getCall(0).args[0][0].params[0].should.eql('stringValue'); - clientBatchStub.getCall(0).args[0][0].params[1].should.eql('test_string'); - clientBatchStub.getCall(0).args[0][1].params[0].should.eql('objectValue'); - clientBatchStub.getCall(0).args[0][1].params[1].should.eql(JSON.stringify(objectToSave)); - }); - }); - - describe('get all config', () => { - it('should succeed get config', async () => { - clientExecuteStub.resolves(new Promise((resolve, reject) => { - resolve({}); - })); - let query = 'SELECT* FROM config'; - await cassandraClient.getConfig(); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - }); - }); - describe('handle cassandra delete ', () => { - it('should succeed delete', async () => { - const query = 'DELETE FROM config WHERE key=?;'; - clientExecuteStub.resolves([]); - await cassandraClient.deleteConfig('delete_key'); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('delete_key'); - }); - }); - - describe('get config by value multple ', () => { - it('should succeed get config', async () => { - clientExecuteStub.resolves(new Promise((resolve, reject) => { - resolve({}); - })); - let query = 'SELECT* FROM config WHERE key= ?'; - await cassandraClient.getConfigValue('value_test'); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('value_test'); - }); - }); - - describe('handle cassandra execute error ', () => { - it('should reject request with error', async () => { - clientExecuteStub.throws(); - let errorText = 'Error occurred in communication with cassandra'; - - cassandraClient.getConfigValue('value_test').then(() => { - throw new Error('Expected to catch error!'); - }, (err) => { - should(err.message).eql(errorText); - }); - }); - }); - describe('handle cassandra batch error ', () => { - it('should reject request with error', async () => { - clientBatchStub.throws(); - let errorText = 'Error occurred in communication with cassandra'; - - cassandraClient.updateConfig({}).then(() => { - throw new Error('Expected to catch error!'); - }, (err) => { - should(err.message).eql(errorText); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/unit-tests/configManager/configHandler-test.js b/tests/unit-tests/configManager/configHandler-test.js index e0bd8b52c..63d87de03 100644 --- a/tests/unit-tests/configManager/configHandler-test.js +++ b/tests/unit-tests/configManager/configHandler-test.js @@ -118,15 +118,15 @@ const resultAfterConvert = { describe('Manager config', function () { let sandbox; - let cassandraGetStub; - let cassandraGetValueStub; - let cassandraUpdateStub; + let databaseConnectorGetStub; + let databaseConnectorGetValueStub; + let databaseConnectorUpdateStub; before(() => { sandbox = sinon.sandbox.create(); - cassandraGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); - cassandraGetValueStub = sandbox.stub(databaseConnector, 'getConfigValue'); - cassandraUpdateStub = sandbox.stub(databaseConnector, 'updateConfig'); + databaseConnectorGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); + databaseConnectorGetValueStub = sandbox.stub(databaseConnector, 'getConfigValue'); + databaseConnectorUpdateStub = sandbox.stub(databaseConnector, 'updateConfig'); manager = rewire('../../../src/configManager/models/configHandler'); }); @@ -140,7 +140,7 @@ describe('Manager config', function () { describe('get default config', function () { it('get default config success', async () => { - cassandraGetStub.resolves([]); + databaseConnectorGetStub.resolves([]); let result = await manager.getConfig(); @@ -152,7 +152,7 @@ describe('Manager config', function () { describe('get config from default and DB', function () { it('get config success', async () => { - cassandraGetStub.resolves({ 'runner_cpu': 2 }); + databaseConnectorGetStub.resolves({ 'runner_cpu': 2 }); let result = await manager.getConfig(); should(Object.keys(result).length).eql(Object.keys(configConstants).length); Object.keys(result).forEach(key => { @@ -166,7 +166,7 @@ describe('Manager config', function () { describe('get config with corrupted data from DB', function () { it('get config success', async () => { - cassandraGetStub.resolves({ 'key_not_valid': 2 }); + databaseConnectorGetStub.resolves({ 'key_not_valid': 2 }); let result = await manager.getConfig(); const resultEscapedUndefined = escapeUndefinedValues(result); should(resultEscapedUndefined).eql(defaultConfig); @@ -175,7 +175,7 @@ describe('Manager config', function () { describe('get config and parse types, types are valid', function () { it('get config success', async () => { - cassandraGetStub.resolves(configResponseParseObject); + databaseConnectorGetStub.resolves(configResponseParseObject); let result = await manager.getConfig(); @@ -186,7 +186,7 @@ describe('Manager config', function () { describe('get config value from env variables', function () { it('get config value success', async () => { - cassandraGetValueStub.resolves(undefined); + databaseConnectorGetValueStub.resolves(undefined); let result = await manager.getConfigValue('runner_cpu'); should(result).eql(1); @@ -195,7 +195,7 @@ describe('Manager config', function () { describe('update config ', function () { it('update config success', async () => { - cassandraUpdateStub.resolves([]); + databaseConnectorUpdateStub.resolves([]); let result = await manager.updateConfig({ runner_cpu: 'test_runner_cpu' }); should(result).eql([]); diff --git a/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js b/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js index 5b6e164be..fe3c21c0e 100644 --- a/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js +++ b/tests/unit-tests/configManager/configHandlerEnvVaribles-test.js @@ -11,11 +11,11 @@ const configConstants = require('../../../src/common/consts').CONFIG; describe('Manager config with env variables', function () { let sandbox; let manager; - let cassandraGetStub; + let databaseConnectorGetStub; before(() => { sandbox = sinon.sandbox.create(); - cassandraGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); + databaseConnectorGetStub = sandbox.stub(databaseConnector, 'getConfigAsObject'); process.env.SMTP_FROM = 'smtp_from_test'; process.env.SMTP_PORT = 'smtp_port_test'; @@ -50,7 +50,7 @@ describe('Manager config with env variables', function () { sandbox.restore(); }); it('get config for from env varibles in the right types (json,int,float,string)', async () => { - cassandraGetStub.resolves([]); + databaseConnectorGetStub.resolves([]); let result = await manager.getConfig(); should(Object.keys(result).length).eql(Object.keys(configConstants).length); should(result.grafana_url).eql('url_test'); diff --git a/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js b/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js index 4e9eba8ab..8bc604344 100644 --- a/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js +++ b/tests/unit-tests/configManager/sequelize/sequelizeConnector-test.js @@ -4,7 +4,7 @@ const sinon = require('sinon'), databaseConfig = require('../../../../src/config/databaseConfig'), sequelizeConnector = require('../../../../src/configManager/models/database/sequelize/sequelizeConnector'); -describe('Cassandra client tests', function () { +describe('Sequelize client tests', function () { let sandbox, sequelizeModelStub, sequelizeUpsertStub, @@ -123,4 +123,4 @@ describe('Cassandra client tests', function () { should(sequelizeGeValueetStub.args[0][0].where.key).eql('key_value'); }); }); -}); \ No newline at end of file +}); diff --git a/tests/unit-tests/env-test.js b/tests/unit-tests/env-test.js index be029e400..6c9d4934e 100644 --- a/tests/unit-tests/env-test.js +++ b/tests/unit-tests/env-test.js @@ -60,7 +60,7 @@ describe.skip('Env Suite', function () { it('Should Failed - missing all mandatory env', () => { should(logErrorStub.called).eql(true); - should(logErrorStub.args[0][0]).eql('DATABASE_TYPE should be one of: CASSANDRA,MYSQL,POSTGRES,MSSQL,SQLITE'); + should(logErrorStub.args[0][0]).eql('DATABASE_TYPE should be one of: MYSQL,POSTGRES,MSSQL,SQLITE'); }); MANDATORY_VARS.forEach(function (varb) { diff --git a/tests/unit-tests/files/models/cassandraConnector-test.js b/tests/unit-tests/files/models/cassandraConnector-test.js deleted file mode 100644 index 06f476db2..000000000 --- a/tests/unit-tests/files/models/cassandraConnector-test.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let should = require('should'); -let cassandraClient = require('../../../../src/files/models/database/cassandra/cassandraConnector'); -let uuid = require('uuid'); - -describe('Cassandra client tests', function () { - let sandbox; - let clientExecuteStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(); - cassandraClient.init({ execute: clientExecuteStub }); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - }); - - describe('Create and get files', function () { - it('should succeed simple insert of file', async () => { - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - - let query = 'INSERT INTO files(id,name,file) values(?,?,?)'; - await cassandraClient.saveFile(id, 'some_file.txt', 'contentcontentcontent'); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - clientExecuteStub.getCall(0).args[1][1].should.eql('some_file.txt'); - clientExecuteStub.getCall(0).args[1][2].should.eql('contentcontentcontent'); - }); - it('should succeed simple get of file', async () => { - clientExecuteStub.resolves({ rows: [ { name: 'file.txt', file: 'abcdef' }] }); - let id = uuid.v4(); - - let query = 'SELECT id, name FROM files WHERE id = ?'; - let file = await cassandraClient.getFile(id); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - - file.name.should.eql('file.txt'); - file.file.should.eql('abcdef'); - }); - - it('should succeed simple get of file with content', async () => { - clientExecuteStub.resolves({ rows: [ { name: 'file.txt', file: 'abcdef' }] }); - let id = uuid.v4(); - - let query = 'SELECT * FROM files WHERE id = ?'; - let file = await cassandraClient.getFile(id, true); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - - file.name.should.eql('file.txt'); - file.file.should.eql('abcdef'); - }); - - it('should return undefined when file not found', async () => { - clientExecuteStub.resolves({ rows: [] }); - let id = uuid.v4(); - - let query = 'SELECT id, name FROM files WHERE id = ?'; - let file = await cassandraClient.getFile(id); - - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - - should(file).eql(undefined); - }); - }); -}); diff --git a/tests/unit-tests/files/models/database-test.js b/tests/unit-tests/files/models/database-test.js index d32555e36..9155984ec 100644 --- a/tests/unit-tests/files/models/database-test.js +++ b/tests/unit-tests/files/models/database-test.js @@ -1,7 +1,6 @@ const should = require('should'), sinon = require('sinon'), - cassandra = require('../../../../src/files/models/database/cassandra/cassandraConnector'), sequelizeConnector = require('../../../../src/files/models/database/sequelize/sequelizeConnector'), rewire = require('rewire'), databaseConfig = require('../../../../src/config/databaseConfig'); @@ -20,10 +19,9 @@ const functions = [ describe('Testing database', function () { let sandbox; before(function () { - process.env.DATABASE_TYPE = 'CASSANDRA'; + process.env.DATABASE_TYPE = 'SQLITE'; sandbox = sinon.sandbox.create(); functions.forEach(function (func) { - sandbox.stub(cassandra, func.functionName); sandbox.stub(sequelizeConnector, func.functionName); }); }); @@ -34,18 +32,6 @@ describe('Testing database', function () { sandbox.restore(); }); - describe('when database type is cassandra - should applied functions on cassandra client', function () { - before(async function () { - databaseConfig.type = 'cassandra'; - database = rewire('../../../../src/files/models/database'); - }); - functions.forEach(function (func) { - it(`checking func: ${func.functionName}`, async function () { - await database[func.functionName](...func.args); - should(cassandra[func.functionName].args).eql([func.args]); - }); - }); - }); describe('when database type is not cassandra - should applied functions on sequlize client', function () { before(async function () { databaseConfig.type = 'not-cassandra'; diff --git a/tests/unit-tests/jobs/cassandra/cassandra-test.js b/tests/unit-tests/jobs/cassandra/cassandra-test.js deleted file mode 100644 index 3f07ce2e2..000000000 --- a/tests/unit-tests/jobs/cassandra/cassandra-test.js +++ /dev/null @@ -1,230 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let should = require('should'); -let cassandraClient = rewire('../../../../src/jobs/models/database/cassandra/cassandraConnector'); - -let uuid = require('uuid'); - -describe('Cassandra client tests', function() { - let sandbox; - let clientExecuteStub; - let revert; - let loggerErrorStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('Init and shutdown tests', function(){ - it('it should initialize cassandra client successfully', (done) => { - try { - cassandraClient.init({ execute: clientExecuteStub }); - } catch (e) { - e.should.be.equal(undefined); - e.should.not.be.instanceOf(Error); - } - done(); - }); - }); - - describe('Insert new test tests', function(){ - it('should succeed simple insert', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let query = 'INSERT INTO jobs(id, test_id, arrival_rate, cron_expression, duration, emails, environment, ramp_to, webhooks, parallelism, max_virtual_users, notes, proxy_url, debug, enabled) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; - return cassandraClient.insertJob(id, { test_id: testId, arrival_rate: 1, duration: 1, cron_expression: '* * * *', emails: {}, environment: 'test', ramp_to: '1', webhooks: 1, parallelism: 3, max_virtual_users: 500, notes: 'hello' }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - clientExecuteStub.getCall(0).args[1][1].should.eql(testId); - }); - }); - - it('should log error for failing inserting new test', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertJob(uuid.v4(), { test_id: uuid.v4(), arrival_rate: 1, duration: 1, cron_expression: '* * * *', emails: {}, environment: 'test', ramp_to: '1', webhooks: 1, notes: 'hello' }) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get jobs', function(){ - it('should get multiple jobs', function(){ - let cassandraResponse = { rows: [{ id: 'id', test_id: 'test_id', arrival_rate: 1, duration: 1, cron_expression: null, emails: null, webhooks: null, ramp_to: '1' }] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM jobs'; - return cassandraClient.getJobs() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM jobs'; - return cassandraClient.getJobs() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get job', function(){ - it('should get single job', function(){ - clientExecuteStub.resolves({ rows: [{ id: 'id', test_id: 'test_id', arrival_rate: 1, duration: 1, cron_expression: null, emails: null, webhooks: null, ramp_to: '1' }] }); - let jobId = uuid.v4(); - let query = 'SELECT * FROM jobs WHERE id=?'; - return cassandraClient.getJob(jobId) - .then(function(result){ - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(jobId); - }); - }); - }); - - describe('Delete job', function(){ - it('should delete single job', function(){ - clientExecuteStub.resolves({ rows: [] }); - let jobId = uuid.v4(); - let query = 'DELETE FROM jobs WHERE id=?'; - return cassandraClient.deleteJob(jobId) - .then(function(result){ - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(jobId); - result.should.eql([]); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - let jobId = uuid.v4(); - let query = 'DELETE FROM jobs WHERE id=?'; - return cassandraClient.deleteJob(jobId) - .then(function(){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Update job', function(){ - it('should succeed update of one parameter', function(){ - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - cassandraClient.__set__('databaseConfig', { name: 'keyspace' }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let updateQuery = 'UPDATE jobs SET test_id=? WHERE id=? IF EXISTS'; - let columnQuery = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(id, { test_id: testId }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(columnQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - clientExecuteStub.getCall(1).args[0].should.eql(updateQuery); - clientExecuteStub.getCall(1).args[1][1].should.eql(id); - clientExecuteStub.getCall(1).args[1][0].should.eql(testId); - }); - }); - - it('should succeed update more than one parameter', function(){ - cassandraClient.__set__('columns', undefined); - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }, { column_name: 'duration' }] }); - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let query = 'UPDATE jobs SET test_id=?, duration=? WHERE id=? IF EXISTS'; - let columnQuery = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(id, { test_id: testId, duration: 4 }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(columnQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - clientExecuteStub.getCall(1).args[0].should.eql(query); - clientExecuteStub.getCall(1).args[1][2].should.eql(id); - clientExecuteStub.getCall(1).args[1][1].should.eql(4); - clientExecuteStub.getCall(1).args[1][0].should.eql(testId); - }); - }); - - it('should ignore none existing parameter in the update', function(){ - cassandraClient.__set__('columns', undefined); - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let testId = uuid.v4(); - - let query = 'UPDATE jobs SET test_id=? WHERE id=? IF EXISTS'; - let columnQuery = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(id, { test_id: testId, duration: 4 }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(columnQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - clientExecuteStub.getCall(1).args[0].should.eql(query); - clientExecuteStub.getCall(1).args[1][1].should.eql(id); - clientExecuteStub.getCall(1).args[1][0].should.eql(testId); - }); - }); - - it('should log error for failing updating new test', function(){ - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - clientExecuteStub.rejects(); - return cassandraClient.updateJob(uuid.v4(), { test_id: uuid.v4() }) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - - ['id', 'job_id'].forEach(function(idName){ - it('should reject an error for trying to update ' + idName, function(){ - cassandraClient.__set__('columns', undefined); - clientExecuteStub.onCall(0).resolves({ rows: [{ column_name: 'test_id' }] }); - cassandraClient.__set__('databaseConfig', { name: 'keyspace' }); - - let query = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ? AND table_name = \'jobs\''; - return cassandraClient.updateJob(uuid.v4(), { [idName]: 'something' }) - .catch(function(err){ - err.statusCode.should.eql(400); - err.message.should.eql('Job id can not be updated'); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('keyspace'); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/unit-tests/jobs/models/jobManager-test.js b/tests/unit-tests/jobs/models/jobManager-test.js index c59016ed3..67ca0d5d3 100644 --- a/tests/unit-tests/jobs/models/jobManager-test.js +++ b/tests/unit-tests/jobs/models/jobManager-test.js @@ -111,7 +111,7 @@ const jobBodyWithCustomEnvVars = { describe('Manager tests', function () { let sandbox; - let cassandraInsertStub; + let databaseConnectorInsertStub; let loggerErrorStub; let loggerInfoStub; let jobConnectorRunJobStub; @@ -119,10 +119,10 @@ describe('Manager tests', function () { let jobGetLogsStub; let jobDeleteContainerStub; let uuidStub; - let cassandraDeleteStub; - let cassandraGetStub; - let cassandraGetSingleJobStub; - let cassandraUpdateJobStub; + let databaseConnectorDeleteStub; + let databaseConnectorGetStub; + let databaseConnectorGetSingleJobStub; + let databaseConnectorUpdateJobStub; let dockerHubConnectorGetMostRecentTagStub; let jobTemplateCreateJobRequestStub; let getConfigValueStub; @@ -130,11 +130,11 @@ describe('Manager tests', function () { before(() => { sandbox = sinon.sandbox.create(); - cassandraInsertStub = sandbox.stub(databaseConnector, 'insertJob'); - cassandraGetStub = sandbox.stub(databaseConnector, 'getJobs'); - cassandraGetSingleJobStub = sandbox.stub(databaseConnector, 'getJob'); - cassandraDeleteStub = sandbox.stub(databaseConnector, 'deleteJob'); - cassandraUpdateJobStub = sandbox.stub(databaseConnector, 'updateJob'); + databaseConnectorInsertStub = sandbox.stub(databaseConnector, 'insertJob'); + databaseConnectorGetStub = sandbox.stub(databaseConnector, 'getJobs'); + databaseConnectorGetSingleJobStub = sandbox.stub(databaseConnector, 'getJob'); + databaseConnectorDeleteStub = sandbox.stub(databaseConnector, 'deleteJob'); + databaseConnectorUpdateJobStub = sandbox.stub(databaseConnector, 'updateJob'); jobGetLogsStub = sandbox.stub(jobConnector, 'getLogs'); jobDeleteContainerStub = sandbox.stub(jobConnector, 'deleteAllContainers'); jobStopRunStub = sandbox.stub(jobConnector, 'stopRun'); @@ -174,21 +174,21 @@ describe('Manager tests', function () { dockerHubConnectorGetMostRecentTagStub.resolves(); }); - it('Cassandra connector returns an empty array', async () => { - cassandraGetStub.resolves([]); + it('databaseConnector returns an empty array', async () => { + databaseConnectorGetStub.resolves([]); await manager.reloadCronJobs(); manager.__get__('cronJobs').should.eql({}); }); - it('Cassandra connector returns an array with job with no schedules', async () => { - cassandraGetStub.resolves([{ cron_expression: null }]); + it('databaseConnector returns an array with job with no schedules', async () => { + databaseConnectorGetStub.resolves([{ cron_expression: null }]); await manager.reloadCronJobs(); manager.__get__('cronJobs').should.eql({}); }); - describe('Cassandra connector returns an array with job with schedules, and job exist and can be run', function () { + describe('databaseConnector returns an array with job with schedules, and job exist and can be run', function () { it('Verify job added', async function () { - cassandraGetStub.resolves([jobBodyWithCron]); + databaseConnectorGetStub.resolves([jobBodyWithCron]); await manager.reloadCronJobs(); manager.__get__('cronJobs').should.have.key('5a9eee73-cf56-47aa-ac77-fad59e961aaa'); }); @@ -255,9 +255,9 @@ describe('Manager tests', function () { uuidStub.returns('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); }); - it('Simple request with custom env vars, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request with custom env vars, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { jobConnectorRunJobStub.resolves({ id: 'run_id' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', @@ -277,7 +277,7 @@ describe('Manager tests', function () { let jobResponse = await manager.createJob(jobBodyWithCustomEnvVars); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); jobTemplateCreateJobRequestStub.args[0][3].should.containEql({ JOB_ID: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', @@ -295,9 +295,9 @@ describe('Manager tests', function () { jobTemplateCreateJobRequestStub.args[0][3].should.have.key('RUN_ID'); }); - it('Simple request, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', ramp_to: '1', @@ -311,13 +311,13 @@ describe('Manager tests', function () { let jobResponse = await manager.createJob(jobBodyWithoutCron); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); }); - it('Simple request, with parallelism, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request, with parallelism, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', ramp_to: '150', @@ -333,7 +333,7 @@ describe('Manager tests', function () { let jobResponse = await manager.createJob(jobBodyWithParallelismThatSplitsNicely); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); should(jobConnectorRunJobStub.args[0][0].spec.parallelism).eql(3); @@ -354,7 +354,7 @@ describe('Manager tests', function () { it('Simple request, with parallelism, and arrival rate splits with decimal point, should round up', async () => { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', ramp_to: '150', @@ -370,7 +370,7 @@ describe('Manager tests', function () { let jobResponse = await manager.createJob(jobBodyWithParallelismThatSplitsWithDecimal); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); should(jobConnectorRunJobStub.args[0][0].spec.parallelism).eql(20); @@ -389,9 +389,9 @@ describe('Manager tests', function () { should(maxVirtualUsers.value).eql('26'); }); - it('Simple request without ramp to, should save new job to cassandra, deploy the job and return the job id and the job configuration', async () => { + it('Simple request without ramp to, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', async () => { jobConnectorRunJobStub.resolves({ id: 'run_id' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', @@ -404,13 +404,13 @@ describe('Manager tests', function () { let jobResponse = await manager.createJob(jobBodyWithoutRampTo); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); }); it('Simple request with enabled as false', async () => { jobConnectorRunJobStub.resolves({ id: 'run_id' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); let expectedResult = { id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: '5a9eee73-cf56-47aa-ac77-fad59e961aaa', @@ -424,25 +424,25 @@ describe('Manager tests', function () { let jobResponse = await manager.createJob(jobBodyWithEnabledFalse); jobResponse.should.containEql(expectedResult); - cassandraInsertStub.callCount.should.eql(1); + databaseConnectorInsertStub.callCount.should.eql(1); jobConnectorRunJobStub.callCount.should.eql(1); }); - it('Fail to save job to cassandra', function () { - cassandraInsertStub.rejects({ error: 'cassandra error' }); + it('Fail to save job to databaseConnector', function () { + databaseConnectorInsertStub.rejects({ error: 'databaseConnector error' }); return manager.createJob(jobBodyWithoutRampTo) .catch(function (error) { jobConnectorRunJobStub.callCount.should.eql(0); loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0].should.eql([{ error: 'cassandra error' }, 'Error occurred trying to create new job']); - error.should.eql({ error: 'cassandra error' }); + loggerErrorStub.args[0].should.eql([{ error: 'databaseConnector error' }, 'Error occurred trying to create new job']); + error.should.eql({ error: 'databaseConnector error' }); }); }); it('Fail to create a job', function () { jobConnectorRunJobStub.rejects({ error: 'job creator error' }); - cassandraInsertStub.resolves({ success: 'success' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); return manager.createJob(jobBodyWithoutRampTo) .catch(function (error) { @@ -453,14 +453,14 @@ describe('Manager tests', function () { }); }); - describe('Request with cron expression, should save new job to cassandra, deploy the job and return the job id and the job configuration', function () { + describe('Request with cron expression, should save new job to databaseConnector, deploy the job and return the job id and the job configuration', function () { before(() => { jobConnectorRunJobStub.resolves({}); }); it('Validate response', function () { - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let expectedResult = { 'cron_expression': '* * * * * *', ramp_to: '1', @@ -494,14 +494,14 @@ describe('Manager tests', function () { }); }); - describe('Request with cron expression and enabled=false should save new job to cassandra, deploy the job and return the job id and the job configuration and not run the job', function () { + describe('Request with cron expression and enabled=false should save new job to databaseConnector, deploy the job and return the job id and the job configuration and not run the job', function () { before(() => { jobConnectorRunJobStub.resolves({}); }); it('Validate response', function () { - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let expectedResult = { 'cron_expression': '* * * * * *', ramp_to: '1', @@ -544,8 +544,8 @@ describe('Manager tests', function () { it('Validate response', function () { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let expectedResult = { cron_expression: '* * * * * *', ramp_to: '1', @@ -581,8 +581,8 @@ describe('Manager tests', function () { describe('Request with cron expression, that is not invoked immediately', function () { it('Validate response', function () { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); let date = new Date(); date.setSeconds(date.getSeconds() + 5); jobBodyWithCronNotImmediately.cron_expression = date.getSeconds() + ' * * * * *'; @@ -649,9 +649,9 @@ describe('Manager tests', function () { it('Should update job successfully', async function () { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraUpdateJobStub.resolves({}); - cassandraGetSingleJobStub.resolves([{ + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorUpdateJobStub.resolves({}); + databaseConnectorGetSingleJobStub.resolves([{ id: '5a9eee73-cf56-47aa-ac77-fad59e961aaf', test_id: 'secondId', environment: 'test', @@ -669,11 +669,11 @@ describe('Manager tests', function () { await manager.deleteJob('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); }); - it('Updating data in cassandra fails', async function () { + it('Updating data in databaseConnector fails', async function () { try { jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraUpdateJobStub.rejects({ error: 'error' }); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorUpdateJobStub.rejects({ error: 'error' }); await manager.createJob(jobBodyWithCron); await manager.updateJob('5a9eee73-cf56-47aa-ac77-fad59e961aaf', { cron_expression: '20 * * * *' }); } catch (error) { @@ -702,12 +702,12 @@ describe('Manager tests', function () { }); uuidStub.returns('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); jobConnectorRunJobStub.resolves({}); - cassandraInsertStub.resolves({ success: 'success' }); - cassandraDeleteStub.resolves({}); + databaseConnectorInsertStub.resolves({ success: 'success' }); + databaseConnectorDeleteStub.resolves({}); await manager.createJob(jobBodyWithCron); await manager.deleteJob('5a9eee73-cf56-47aa-ac77-fad59e961aaf'); - cassandraDeleteStub.callCount.should.eql(1); + databaseConnectorDeleteStub.callCount.should.eql(1); loggerInfoStub.args[2].should.eql(['Job: 5a9eee73-cf56-47aa-ac77-fad59e961aaf completed.']); }); }); @@ -725,7 +725,7 @@ describe('Manager tests', function () { describe('Get jobs', function () { it('Get a list of all jobs - also one time jobs', async function () { - cassandraGetStub.resolves([{ + databaseConnectorGetStub.resolves([{ id: 'id', test_id: 'test_id', environment: 'test', @@ -792,11 +792,11 @@ describe('Manager tests', function () { }]; let jobs = await manager.getJobs(true); jobs.should.eql(expectedResult); - cassandraGetStub.callCount.should.eql(1); + databaseConnectorGetStub.callCount.should.eql(1); }); it('Get a list of jobs - only scheduled jobs', async function () { - cassandraGetStub.resolves([{ + databaseConnectorGetStub.resolves([{ id: 'id', test_id: 'test_id', environment: 'test', @@ -841,34 +841,34 @@ describe('Manager tests', function () { }]; let jobs = await manager.getJobs(); jobs.should.eql(expectedResult); - cassandraGetStub.callCount.should.eql(1); + databaseConnectorGetStub.callCount.should.eql(1); }); it('Get empty list of jobs', async function () { - cassandraGetStub.resolves([]); + databaseConnectorGetStub.resolves([]); let jobs = await manager.getJobs(); jobs.should.eql([]); - cassandraGetStub.callCount.should.eql(1); + databaseConnectorGetStub.callCount.should.eql(1); loggerInfoStub.callCount.should.eql(1); }); - it('Fail to get jobs from cassandra', function () { - cassandraGetStub.rejects({ error: 'cassandra error' }); + it('Fail to get jobs from databaseConnector', function () { + databaseConnectorGetStub.rejects({ error: 'databaseConnector error' }); return manager.getJobs() .catch(function (error) { jobConnectorRunJobStub.callCount.should.eql(0); loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0].should.eql([{ error: 'cassandra error' }, 'Error occurred trying to get jobs']); - error.should.eql({ error: 'cassandra error' }); + loggerErrorStub.args[0].should.eql([{ error: 'databaseConnector error' }, 'Error occurred trying to get jobs']); + error.should.eql({ error: 'databaseConnector error' }); }); }); }); describe('Get job', function () { it('Get a list of jobs', async function () { - cassandraGetSingleJobStub.resolves([{ + databaseConnectorGetSingleJobStub.resolves([{ id: 'id', test_id: 'test_id', environment: 'test', @@ -904,11 +904,11 @@ describe('Manager tests', function () { let job = await manager.getJob('id'); job.should.eql(expectedResult); - cassandraGetSingleJobStub.callCount.should.eql(1); + databaseConnectorGetSingleJobStub.callCount.should.eql(1); }); - it('cassandra returns empty list, should return job not found', function () { - cassandraGetSingleJobStub.resolves([]); + it('databaseConnector returns empty list, should return job not found', function () { + databaseConnectorGetSingleJobStub.resolves([]); return manager.getJob('id') .then(function () { return Promise.reject(new Error('Should not get here')); @@ -919,8 +919,8 @@ describe('Manager tests', function () { }); }); - it('cassandra returns list of few rows, should return error', function () { - cassandraGetSingleJobStub.resolves(['one row', 'second row']); + it('databaseConnector returns list of few rows, should return error', function () { + databaseConnectorGetSingleJobStub.resolves(['one row', 'second row']); return manager.getJob('id') .then(function () { return Promise.reject(new Error('Should not get here')); @@ -931,15 +931,15 @@ describe('Manager tests', function () { }); }); - it('Fail to get jobs from cassandra', function () { - cassandraGetSingleJobStub.rejects({ error: 'cassandra error' }); + it('Fail to get jobs from databaseConnector', function () { + databaseConnectorGetSingleJobStub.rejects({ error: 'databaseConnector error' }); return manager.getJob('id') .catch(function (error) { jobConnectorRunJobStub.callCount.should.eql(0); loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0].should.eql([{ error: 'cassandra error' }, 'Error occurred trying to get job']); - error.should.eql({ error: 'cassandra error' }); + loggerErrorStub.args[0].should.eql([{ error: 'databaseConnector error' }, 'Error occurred trying to get job']); + error.should.eql({ error: 'databaseConnector error' }); }); }); }); diff --git a/tests/unit-tests/processors/cassandra/cassandra-test.js b/tests/unit-tests/processors/cassandra/cassandra-test.js deleted file mode 100644 index 1b39a2881..000000000 --- a/tests/unit-tests/processors/cassandra/cassandra-test.js +++ /dev/null @@ -1,242 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let should = require('should'); -let cassandraClient = rewire('../../../../src/processors/models/database/cassandra/cassandraConnector'); - -let uuid = require('uuid'); - -describe('Cassandra processors tests', function() { - let sandbox; - let clientExecuteStub; - let revert; - let loggerErrorStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('init', function() { - it('should assign the cassandra client successfully', function () { - const prevClient = cassandraClient.__get__('client'); - const newClient = { clientId: 'fake-client' }; - cassandraClient.init(newClient); - const updatedClient = cassandraClient.__get__('client'); - updatedClient.should.equal(newClient); - cassandraClient.__set__('client', prevClient); - }); - }); - - describe('Insert new processor', function(){ - it('should succeed simple insert', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - - let query = cassandraClient._queries.INSERT_PROCESSOR; - return cassandraClient.insertProcessor(id, { name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'' }) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - }); - }); - - it('should log error for failing inserting new processor', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertProcessor('id', { name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'' }) - .catch(function(){ - loggerErrorStub.callCount.should.eql(2); - }); - }); - }); - - describe('Get processors', function(){ - it('should get multiple processors', function(){ - let cassandraResponse = { rows: [{ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'', created_at: Date.now(), updated_at: Date.now() }] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM processors'; - return cassandraClient.getAllProcessors() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get multiple processors while excluding javascript', function(){ - let cassandraResponse = { rows: [{ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'', created_at: Date.now(), updated_at: Date.now() }] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT id, name, description, created_at, updated_at, exported_functions FROM processors'; - return cassandraClient.getAllProcessors(undefined, undefined, 'javascript') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = cassandraClient._queries.GET_ALL_PROCESSORS; - return cassandraClient.getAllProcessors() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get processor', function() { - describe('getProcessorById', function() { - it('should get a single processor', function(){ - clientExecuteStub.resolves({ rows: [{ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'', created_at: Date.now(), updated_at: Date.now() }] }); - let proccesorId = uuid.v4(); - let query = cassandraClient._queries.GET_PROCESSOR_BY_ID; - return cassandraClient.getProcessorById(proccesorId) - .then(function(result){ - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(proccesorId); - should(result).containDeep({ id: 'id', name: 'mick', description: 'some processor', javascript: 'module.exports.mick = \'ey\'' }); - }); - }); - }); - - describe('updateProcessor', function() { - it('should update the execute the update query sucessfully', async function() { - const getProcessorQuery = cassandraClient._queries.GET_PROCESSOR_BY_ID; - const updateProcessorQuery = cassandraClient._queries.UPDATE_PROCESSOR; - const deleteProcessorMapping = cassandraClient._queries.DELETE_PROCESSOR_MAPPING; - const insertProcessorMapping = cassandraClient._queries.INSERT_PROCESSOR_MAPPING; - const processorId = uuid.v4(); - const processor = { - id: processorId, - name: 'updated processor name', - description: 'some processor', - javascript: 'module.exports.mick = \'ey\'', - created_at: Date.now() - }; - const processorMapping = { - name: processor.name, - id: processorId - }; - clientExecuteStub.withArgs(getProcessorQuery).resolves({ rows: [processor] }); - clientExecuteStub.withArgs(updateProcessorQuery).resolves({ rows: [processor] }); - clientExecuteStub.withArgs(insertProcessorMapping).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(deleteProcessorMapping).resolves({ rows: [] }); - cassandraClient.updateProcessor(processorId, processor).then(result => { - clientExecuteStub.getCall(0).args[0].should.eql(getProcessorQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql(processorId); - clientExecuteStub.getCall(1).args[0].should.eql(updateProcessorQuery); - clientExecuteStub.getCall(1).args[1][0].should.eql(processor.name); - clientExecuteStub.getCall(2).args[0].should.eql(insertProcessorMapping); - clientExecuteStub.getCall(2).args[1][0].should.eql(processor.name); - clientExecuteStub.getCall(2).args[1][1].should.eql(processor.id); - clientExecuteStub.getCall(3).args[0].should.eql(deleteProcessorMapping); - clientExecuteStub.getCall(3).args[1][0].should.eql(processor.name); - should(result).containDeep(processor); - }); - }); - }); - - describe('getProcessorByName', function() { - it('should get a single processor', function() { - let processorId = uuid.v4(); - let processorName = 'Generate Random Kitty Name'; - const processor = { - id: processorId, - name: processorName, - description: 'some processor', - javascript: 'module.exports.mick = \'ey\'', - created_at: Date.now(), - updated_at: Date.now() - }; - const processorMapping = { - name: processorName, - id: processorId - }; - let query = cassandraClient._queries.GET_PROCESSOR_BY_ID; - let mappingQuery = cassandraClient._queries.GET_PROCESSOR_MAPPING; - clientExecuteStub.withArgs(mappingQuery).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(query).resolves({ rows: [processor] }); - return cassandraClient.getProcessorByName(processorName) - .then(function(result) { - clientExecuteStub.getCall(0).args[0].should.eql(mappingQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql(processorName); - clientExecuteStub.getCall(1).args[0].should.eql(query); - clientExecuteStub.getCall(1).args[1][0].should.eql(processorId); - should(result).containDeep(processor); - }); - }); - }); - }); - - describe('Delete processor', function(){ - it('should delete single processor', function(){ - const processorId = uuid.v4(); - const processorMapping = { - name: 'mick', - id: processorId - }; - const getProcessorQuery = cassandraClient._queries.GET_PROCESSOR_BY_ID; - const deleteProcessorQuery = cassandraClient._queries.DELETE_PROCESSOR; - const deleteMappingQuery = cassandraClient._queries.DELETE_PROCESSOR_MAPPING; - clientExecuteStub.withArgs(getProcessorQuery).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(deleteProcessorQuery).resolves({ rows: [] }); - clientExecuteStub.withArgs(deleteMappingQuery).resolves({ rows: [] }); - - return cassandraClient.deleteProcessor(processorId) - .then(function(result){ - clientExecuteStub.callCount.should.eql(3); - clientExecuteStub.getCall(0).args[0].should.eql(getProcessorQuery); - clientExecuteStub.getCall(0).args[1][0].should.eql(processorMapping.id); - clientExecuteStub.getCall(1).args[0].should.eql(deleteProcessorQuery); - clientExecuteStub.getCall(1).args[1][0].should.eql(processorId); - clientExecuteStub.getCall(2).args[0].should.eql(deleteMappingQuery); - clientExecuteStub.getCall(2).args[1][0].should.eql(processorMapping.name); - result.should.eql([[], []]); - }); - }); - - it('should get failure from cassandra', function(){ - let processorId = uuid.v4(); - const processorMapping = { - id: processorId, - name: 'mick' - }; - let getProcessorMapping = cassandraClient._queries.GET_PROCESSOR_BY_ID; - let deleteProcessorQuery = cassandraClient._queries.DELETE_PROCESSOR; - clientExecuteStub.withArgs(getProcessorMapping).resolves({ rows: [processorMapping] }); - clientExecuteStub.withArgs(deleteProcessorQuery).rejects(new Error('error')); - return cassandraClient.deleteProcessor(processorId) - .then(function(){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(getProcessorMapping); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); -}); diff --git a/tests/unit-tests/reporter/cassandra/cassandra-test.js b/tests/unit-tests/reporter/cassandra/cassandra-test.js deleted file mode 100644 index aa91fdccf..000000000 --- a/tests/unit-tests/reporter/cassandra/cassandra-test.js +++ /dev/null @@ -1,385 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let driver = require('cassandra-driver'); -let rewire = require('rewire'); -let cassandraClient = rewire('../../../../src/reports/models/database/cassandra/cassandraConnector'); - -let uuid = require('uuid'); - -const REPORT = { - 'test_id': 'test id', - 'revision_id': 'revision_id', - 'report_id': 'report_id', - 'test_name': 'test name', - 'report_url': 'http://www.zooz.com', - 'last_stats': JSON.stringify({ - 'timestamp': '2018-05-28T15:40:10.044Z', - 'scenariosCreated': 289448, - 'scenariosCompleted': 289447, - 'requestsCompleted': 694611, - 'latency': { - 'min': 6.3, - 'max': 3822.8, - 'median': 58.8, - 'p95': 115.5, - 'p99': 189.4 - }, - 'rps': { - 'count': 694611, - 'mean': 178.61 - }, - 'scenarioDuration': { - 'min': 80.4, - 'max': 5251.7, - 'median': 146.8, - 'p95': 244.4, - 'p99': 366.6 - }, - 'scenarioCounts': { - 'Create token and get token': 173732, - 'Create token, create customer and assign token to customer': 115716 - }, - 'errors': { EAI_AGAIN: 112, NOTREACH: 123 }, - 'codes': { - '200': 173732, - '201': 520878, - '503': 1 - }, - 'matches': 0, - 'customStats': {}, - 'concurrency': 1510, - 'pendingRequests': 1471 - }), - 'end_time': 1527533519591, - 'start_time': 1527533459591 -}; - -describe('Cassandra client tests', function() { - let sandbox; - let clientExecuteStub; - let revert; - let loggerErrorStub; - - let testId, revisionId, reportId, jobId, testType, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt, phase; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(driver.Client.prototype, 'execute'); - revert = cassandraClient.__set__('client', { execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - - testId = uuid(); - revisionId = uuid(); - reportId = uuid(); - jobId = uuid(); - testType = 'testType'; - startTime = new Date('1/10/2017'); - testName = 'testName'; - testDescription = 'testDescription'; - testConfiguration = 'testConfiguration'; - notes = 'notes'; - lastUpdatedAt = Date.now(); - phase = '0'; - }); - - afterEach(() => { - sandbox.resetHistory(); - clientExecuteStub.reset(); - }); - - after(() => { - sandbox.restore(); - revert(); - }); - - describe('Init and shutdown tests', function(){ - it('it should initialize cassandra client successfully', (done) => { - try { - cassandraClient.init({ execute: clientExecuteStub }); - } catch (e) { - e.should.be.equal(undefined); - e.should.not.be.instanceOf(Error); - } - done(); - }); - }); - - describe('Insert new report', function () { - it('should succeed simple insert', function () { - clientExecuteStub.resolves({ rowLength: 1, rows: [{ '[applied]': true }] }); - let queryReport = 'INSERT INTO reports_summary(test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; - let queryLastReport = 'INSERT INTO last_reports(start_time_year,start_time_month,test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; - return cassandraClient.insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) - .then(function () { - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(queryReport); - clientExecuteStub.getCall(0).args[1][0].should.eql(testId); - clientExecuteStub.getCall(0).args[1][1].should.eql(revisionId); - clientExecuteStub.getCall(0).args[1][2].should.eql(reportId); - clientExecuteStub.getCall(1).args[0].should.eql(queryLastReport); - clientExecuteStub.getCall(1).args[1][0].should.eql(2017); - clientExecuteStub.getCall(1).args[1][1].should.eql(1); - clientExecuteStub.getCall(1).args[1][2].should.eql(testId); - clientExecuteStub.getCall(1).args[1][3].should.eql(revisionId); - clientExecuteStub.getCall(1).args[1][4].should.eql(reportId); - }); - }); - - it('should log error for failing inserting new report', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('should update report with benchmark tests ', async () => { - it('should update report with benchmark tests ', async () => { - clientExecuteStub.resolves({ rowLength: 1, rows: [{ '[applied]': false }] }); - let queryReport = 'UPDATE reports_summary SET score=?, benchmark_weights_data=? WHERE test_id=? AND report_id=?'; - await cassandraClient.updateReportBenchmark(testId, reportId, 5.3, 'some data'); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.callCount.should.eql(3); // query last report should be trig - clientExecuteStub.getCall(1).args[0].should.eql(queryReport); - clientExecuteStub.getCall(1).args[1][0].should.eql(5.3); - clientExecuteStub.getCall(1).args[1][1].should.eql('some data'); - clientExecuteStub.getCall(1).args[1][2].should.eql(testId); - clientExecuteStub.getCall(1).args[1][3].should.eql(reportId); - }); - }); - - describe('Insert report that already exist', function () { - it('should succeed simple insert', function () { - clientExecuteStub.resolves({ rowLength: 1, rows: [{ '[applied]': false }] }); - let queryReport = 'INSERT INTO reports_summary(test_id, revision_id, report_id, job_id, test_type, phase, start_time, test_name, test_description, test_configuration, notes, last_updated_at) values(?,?,?,?,?,?,?,?,?,?,?,?) IF NOT EXISTS'; - return cassandraClient.insertReport(testId, revisionId, reportId, jobId, testType, phase, startTime, testName, testDescription, testConfiguration, notes, lastUpdatedAt) - .then(function () { - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.callCount.should.eql(1); // query last report should not be trig - clientExecuteStub.getCall(0).args[0].should.eql(queryReport); - clientExecuteStub.getCall(0).args[1][0].should.eql(testId); - clientExecuteStub.getCall(0).args[1][1].should.eql(revisionId); - clientExecuteStub.getCall(0).args[1][2].should.eql(reportId); - }); - }); - }); - - describe('Update report and verify last report updated', function () { - it('should succeed simple insert', async function () { - const phase = uuid(); - const cassandraClientLastReport = cassandraClient.__get__('updateLastReportAsync'); - clientExecuteStub.onCall(0).resolves({ rowLength: 1, rows: [{ 'start_time': '01/22/2017' }] }); - clientExecuteStub.onCall(1).resolves({ rowLength: 1 }); - let queryLastReport = 'UPDATE last_reports SET phase=?, last_updated_at=? WHERE start_time_year=? AND start_time_month=? AND start_time=? AND test_id=? AND report_id=?'; - await cassandraClientLastReport(testId, reportId, { phase, last_updated_at: lastUpdatedAt }); - - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(1).args[0].should.eql(queryLastReport); - clientExecuteStub.getCall(1).args[1][0].should.eql(phase); - clientExecuteStub.getCall(1).args[1][1].should.eql(lastUpdatedAt); - clientExecuteStub.getCall(1).args[1][2].should.eql(2017); - clientExecuteStub.getCall(1).args[1][3].should.eql(1); - clientExecuteStub.getCall(1).args[1][4].should.eql('01/22/2017'); - clientExecuteStub.getCall(1).args[1][5].should.eql(testId); - clientExecuteStub.getCall(1).args[1][6].should.eql(reportId); - }); - }); - describe('Get report', function(){ - it('should get single report', function(){ - let cassandraResponse = { rows: [REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; - return cassandraClient.getReport() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; - return cassandraClient.getReport() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get last report success', function(){ - it('should get last reports', function(){ - let cassandraResponse = { rows: [REPORT, REPORT, REPORT] }; - clientExecuteStub.onCall(0).resolves({ rows: [REPORT] }); - clientExecuteStub.onCall(1).resolves({ rows: [REPORT] }); - clientExecuteStub.onCall(2).resolves({ rows: [REPORT] }); - clientExecuteStub.resolves({ rows: [REPORT] }); - - let query = 'SELECT * FROM last_reports WHERE start_time_year=? AND start_time_month=? LIMIT ?'; - return cassandraClient.getLastReports(3) - .then(function (result) { - const date = new Date(); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(date.getFullYear()); - clientExecuteStub.getCall(0).args[1][1].should.eql(date.getMonth() + 1); - clientExecuteStub.getCall(0).args[1][2].should.eql(3); - result.should.eql(cassandraResponse.rows); - sandbox.resetHistory(); - }); - }); - }); - - describe('Get last report fail', function () { - it('should get failure from cassandra', function () { - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=? AND report_id=?'; - return cassandraClient.getReport() - .then(function (result) { - return Promise.reject(new Error('should not get here')); - }).catch(function (err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Get reports', function(){ - it('should get multiple reports', function(){ - let cassandraResponse = { rows: [REPORT, REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'SELECT * FROM reports_summary WHERE test_id=?'; - return cassandraClient.getReports() - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - result.should.eql(cassandraResponse.rows); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=?'; - return cassandraClient.getReports() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Insert new stats', function(){ - const runnerId = uuid(); - const statId = uuid(); - const statsTime = new Date().getTime(); - const phaseIndex = uuid(0); - const phaseStatus = uuid('started'); - const data = JSON.stringify({ median: 5 }); - - it('should succeed simple insert', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let query = 'INSERT INTO reports_stats(runner_id, test_id, report_id, stats_id, stats_time, phase_index, phase_status, data) values(?,?,?,?,?,?,?,?)'; - return cassandraClient.insertStats(runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data) - .then(function(){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(runnerId); - clientExecuteStub.getCall(0).args[1][1].should.eql(testId); - clientExecuteStub.getCall(0).args[1][2].should.eql(reportId); - clientExecuteStub.getCall(0).args[1][3].should.eql(statId); - }); - }); - - it('should log error for failing inserting new report', function(){ - clientExecuteStub.rejects(); - return cassandraClient.insertStats(runnerId, testId, reportId, statId, statsTime, phaseIndex, phaseStatus, data) - .catch(function(){ - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Subscribe Runner', function(){ - it('should subscribe runner to report', function(){ - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - - let query = 'INSERT INTO report_subscribers(test_id, report_id, runner_id, phase_status) values(?,?,?,?)'; - return cassandraClient.subscribeRunner('test_id', 'report_id', 'runner_id', 'initializing') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('test_id'); - clientExecuteStub.getCall(0).args[1][1].should.eql('report_id'); - clientExecuteStub.getCall(0).args[1][2].should.eql('runner_id'); - clientExecuteStub.getCall(0).args[1][3].should.eql('initializing'); - }); - }); - - it('should get failure from cassandra', function(){ - clientExecuteStub.rejects(new Error('error')); - - let query = 'SELECT * FROM reports_summary WHERE test_id=?'; - return cassandraClient.getReports() - .then(function(result){ - return Promise.reject(new Error('should not get here')); - }).catch(function(err) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - loggerErrorStub.args[0][1].message.should.eql('error'); - err.message.should.eql('Error occurred in communication with cassandra'); - }); - }); - }); - - describe('Update Subscriber', function(){ - it('should update subscriber stage in report without stats', function(){ - let cassandraResponse = { rows: [REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'UPDATE report_subscribers SET phase_status=? WHERE test_id=? AND report_id=? AND runner_id=?'; - return cassandraClient.updateSubscriber('test_id', 'report_id', 'runner_id', 'intermediate') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('intermediate'); - clientExecuteStub.getCall(0).args[1][1].should.eql('test_id'); - clientExecuteStub.getCall(0).args[1][2].should.eql('report_id'); - clientExecuteStub.getCall(0).args[1][3].should.eql('runner_id'); - }); - }); - - it('should update subscriber stage in report with stats', function(){ - let cassandraResponse = { rows: [REPORT] }; - clientExecuteStub.resolves(cassandraResponse); - - let query = 'UPDATE report_subscribers SET phase_status=?, last_stats=? WHERE test_id=? AND report_id=? AND runner_id=?'; - return cassandraClient.updateSubscriberWithStats('test_id', 'report_id', 'runner_id', 'intermediate', 'last_stats') - .then(function(result){ - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql('intermediate'); - clientExecuteStub.getCall(0).args[1][1].should.eql('last_stats'); - clientExecuteStub.getCall(0).args[1][2].should.eql('test_id'); - clientExecuteStub.getCall(0).args[1][3].should.eql('report_id'); - clientExecuteStub.getCall(0).args[1][4].should.eql('runner_id'); - }); - }); - }); -}); diff --git a/tests/unit-tests/reporter/models/finalReportGenerator-test.js b/tests/unit-tests/reporter/models/finalReportGenerator-test.js index a8f119080..cd0a0c86d 100644 --- a/tests/unit-tests/reporter/models/finalReportGenerator-test.js +++ b/tests/unit-tests/reporter/models/finalReportGenerator-test.js @@ -201,7 +201,7 @@ describe('Artillery report generator test', () => { }); describe('Bad flows - With parallelism', function () { - it('create final report fails when cassandra returns error', async () => { + it('create final report fails when DB returns error', async () => { databaseConnectorGetStatsStub.rejects(new Error('Database failure')); reportsManagerGetReportStub.resolves(REPORT); @@ -216,7 +216,7 @@ describe('Artillery report generator test', () => { testShouldFail.should.eql(false, 'Test action was supposed to get exception'); }); - it('create final report fails when no rows returned from cassandra ', async () => { + it('create final report fails when no rows returned from DB ', async () => { databaseConnectorGetStatsStub.resolves([]); reportsManagerGetReportStub.resolves(REPORT); @@ -336,4 +336,4 @@ const PARALLEL_INTERMEDIATE_ROWS = [ 'phase_index': '0', 'data': '{"timestamp":"2019-03-10T17:24:33.043Z","scenariosCreated":300,"scenariosCompleted":300,"requestsCompleted":300,"latency":{"min":59.5,"max":98.3,"median":61.3,"p95":72.9,"p99":84},"rps":{"count":300,"mean":20},"scenarioDuration":{"min":60,"max":98.9,"median":61.9,"p95":73.5,"p99":84.5},"scenarioCounts":{"Get response code 200":300},"errors":{},"codes":{"200":300},"matches":0,"customStats":{},"counters":{},"concurrency":1,"pendingRequests":1,"scenariosAvoided":0}' } -]; \ No newline at end of file +]; diff --git a/tests/unit-tests/tests/models/cassandraConnector-test.js b/tests/unit-tests/tests/models/cassandraConnector-test.js deleted file mode 100644 index d70b5b96a..000000000 --- a/tests/unit-tests/tests/models/cassandraConnector-test.js +++ /dev/null @@ -1,566 +0,0 @@ -'use strict'; -let sinon = require('sinon'); -let logger = require('../../../../src/common/logger'); -let should = require('should'); -let cassandraClient = require('../../../../src/tests/models/database/cassandra/cassandraConnector'); -let uuid = require('uuid'); -let uuidCassandraDriver = require('cassandra-driver').types.Uuid; - -describe('Cassandra client tests', function () { - let sandbox; - let clientExecuteStub; - let loggerErrorStub; - - before(() => { - sandbox = sinon.sandbox.create(); - clientExecuteStub = sandbox.stub(); - cassandraClient.init({ execute: clientExecuteStub }); - loggerErrorStub = sandbox.stub(logger, 'error'); - }); - - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - }); - - describe('Insert new test tests', function () { - it('should succeed simple insert', function () { - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - let revisionId = uuid.v4(); - - let query = 'INSERT INTO tests(id, name, description, type, updated_at, raw_data, artillery_json, revision_id, file_id, csv_file_id, processor_id) values(?,?,?,?,?,?,?,?,?,?,?)'; - return cassandraClient.insertTest({ scenarios: { raw_data: 'raw' } }, { json: 'artillery' }, id, revisionId) - .then(function () { - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - clientExecuteStub.getCall(0).args[1][7].should.eql(revisionId); - clientExecuteStub.getCall(0).args[1][5].should.eql(JSON.stringify({ 'scenarios': { 'raw_data': 'raw' } })); - clientExecuteStub.getCall(0).args[1][6].should.eql(JSON.stringify({ json: 'artillery' })); - }); - }); - - it('should log error for failing inserting new test', function () { - clientExecuteStub.rejects(); - return cassandraClient.insertTest({ data: 'raw' }, { json: 'artillery' }, uuid.v4(), uuid.v4()) - .catch(function () { - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get single test', function () { - it('Should get single test', function () { - let query = 'SELECT * FROM tests WHERE id = ? ORDER BY updated_at DESC limit 1'; - let date = new Date(); - let cassandraResponse = { - rows: [ - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: date, - raw_data: '{"data":"raw"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ab' - } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - should(clientExecuteStub.getCall(0).args[1]).eql([uuidCassandraDriver.fromString('c1656c48-e028-11e7-80c1-9a214cf093aa')]); - should(JSON.stringify(res)).eql(JSON.stringify(cassandraResponse.rows[0])); - }); - }); - - it('Should get error because of cassandra error', function () { - let query = 'SELECT * FROM tests WHERE id = ? ORDER BY updated_at DESC limit 1'; - clientExecuteStub.rejects(); - return cassandraClient.getTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('benchmark tests', () => { - it('should succeed insert benchmark', async () => { - clientExecuteStub.resolves({ result: { rowLength: 0 } }); - let id = uuid.v4(); - - let query = 'INSERT INTO benchmarks(test_id,data) values(?,?)'; - await cassandraClient.insertTestBenchmark(id, JSON.stringify({ data: 'some data' })); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(uuidCassandraDriver.fromString(id)); - clientExecuteStub.getCall(0).args[1][1].should.eql(JSON.stringify({ data: 'some data' })); - }); - it('should get benchmark', async () => { - clientExecuteStub.resolves({ rows: [{ data: 'some data' }] }); - let id = uuid.v4(); - - let query = 'SELECT * FROM benchmarks WHERE test_id=?'; - await cassandraClient.getTestBenchmark(id); - loggerErrorStub.callCount.should.eql(0); - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].should.eql(id); - }); - }); - - describe('Delete test', function () { - it('Should delete single test successfully', () => { - let query = 'DELETE FROM tests WHERE id=?'; - let cassandraResponse = {}; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.deleteTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1].should.eql(['c1656c48-e028-11e7-80c1-9a214cf093aa']); - res.should.eql(cassandraResponse); - }); - }); - it('Should get error because of cassandra error', function () { - let query = 'DELETE FROM tests WHERE id=?'; - clientExecuteStub.rejects(); - return cassandraClient.deleteTest('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get all test revisions', function () { - it('Should get test revisions', function () { - let query = 'SELECT * FROM tests WHERE id = ?'; - let date = new Date(); - let laterDate = new Date(); - let cassandraResponse = { - rows: [ - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: date, - raw_data: '{"data":"raw"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ab' - }, - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: laterDate, - raw_data: '{"data":"raw"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ac' - } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getAllTestRevisions('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - res.should.eql(cassandraResponse.rows); - }).catch(function (err) { - throw new Error('Should not get here: ' + err); - }); - }); - - it('Should get error because of cassandra error', function () { - let query = 'SELECT * FROM tests WHERE id = ?'; - clientExecuteStub.rejects(); - return cassandraClient.getAllTestRevisions('c1656c48-e028-11e7-80c1-9a214cf093aa') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - clientExecuteStub.getCall(0).args[1][0].toString().should.eql('c1656c48-e028-11e7-80c1-9a214cf093aa'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - - describe('Get all tests', function () { - it('Should get all tests', function () { - let query = 'SELECT * FROM tests'; - let date = new Date(); - let laterDate = new Date(); - let cassandraResponse = { - rows: [ - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093aa', - updated_at: date, - raw_data: '{"name":"Test1","description":"Test1"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ab' - }, - { - id: 'c1656c48-e028-11e7-80c1-9a214cf093ab', - updated_at: laterDate, - raw_data: '{"name":"Test2","description":"Test2"}', - artillery_json: '{"json":"artillery"}', - revision_id: 'c1656c48-e028-11e7-80c1-9a214cf093ac' - } - ] - }; - - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getTests() - .then(function (res) { - clientExecuteStub.getCall(0).args[0].should.eql(query); - res.should.eql(cassandraResponse.rows); - }).catch(function (err) { - throw new Error('Should not get here: ' + err); - }); - }); - - it('Should get error because of cassandra error', function () { - let query = 'SELECT * FROM tests'; - clientExecuteStub.rejects(); - return cassandraClient.getTests() - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql(query); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('Get dsl definition', function () { - it('Should get definition object', function () { - const cassandraResponse = { - rows: [ - { artillery_json: '{"json":"artillery"}' } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql({ - 'artillery_json': { - 'json': 'artillery' - } - }); - }); - }); - it('Should get definition undefined when there is no result', function () { - const cassandraResponse = { - rows: [] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(undefined); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.getDslDefinition('dslName', 'definitionName') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('SELECT * FROM dsl WHERE dsl_name = ? AND definition_name = ? limit 1'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('Get dsl definitions', function () { - it('Should get array of definition object', function () { - const cassandraResponse = { - rows: [ - { artillery_json: '{"json":"artillery"}' }, - { artillery_json: '{"json":"artillery2"}' } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinitions('dslName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ?', - [ - 'dslName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql([ - { - 'artillery_json': { - 'json': 'artillery' - } - }, - { - 'artillery_json': { - 'json': 'artillery2' - } - } - ]); - }); - }); - it('Should get empty array when there is no result', function () { - const cassandraResponse = { - rows: [] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.getDslDefinitions('dslName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'SELECT * FROM dsl WHERE dsl_name = ?', - [ - 'dslName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql([]); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.getDslDefinitions('dslName') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('SELECT * FROM dsl WHERE dsl_name = ?'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('update dsl definition', function () { - it('Should get true when update applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': true } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.updateDslDefinition('dslName', 'definitionName', { json: 'artillery' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - '{"json":"artillery"}', - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(true); - }); - }); - it('Should get false when update does not applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': false } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.updateDslDefinition('dslName', 'definitionName', { json: 'artillery' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - '{"json":"artillery"}', - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(false); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.updateDslDefinition('dslName', 'definitionName', { json: 'artillery' }) - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('UPDATE dsl SET artillery_json= ? WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('delete dsl definition', function () { - it('Should get true when delete applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': true } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.deleteDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(true); - }); - }); - it('Should get false when delete does not applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': false } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.deleteDefinition('dslName', 'definitionName') - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;', - [ - 'dslName', - 'definitionName' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(false); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.deleteDefinition('dslName', 'definitionName') - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('DELETE FROM dsl WHERE dsl_name = ? AND definition_name = ? IF EXISTS;'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); - describe('insertDslDefinition definition', function () { - it('Should get true when insert applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': true } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.insertDslDefinition('dslName', 'definitionName', { data: 'data' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS', - [ - 'dslName', - 'definitionName', - '{"data":"data"}' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(true); - }); - }); - it('Should get false when insert applied does not applied', function () { - const cassandraResponse = { - rows: [ - { '[applied]': false } - ] - }; - clientExecuteStub.resolves(cassandraResponse); - return cassandraClient.insertDslDefinition('dslName', 'definitionName', { data: 'data' }) - .then(function (res) { - should(clientExecuteStub.args).eql([ - [ - 'INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS', - [ - 'dslName', - 'definitionName', - '{"data":"data"}' - ], - { - 'consistency': 6, - 'prepare': true - } - ] - ]); - should(res).eql(false); - }); - }); - - it('Should get error because of cassandra error', function () { - clientExecuteStub.rejects(); - return cassandraClient.insertDslDefinition('dslName', 'definitionName', { data: 'data' }) - .then(function () { - throw new Error('Should not get here'); - }).catch(function () { - clientExecuteStub.getCall(0).args[0].should.eql('INSERT INTO dsl(dsl_name, definition_name, artillery_json) values(?,?,?) IF NOT EXISTS'); - loggerErrorStub.callCount.should.eql(1); - }); - }); - }); -}); diff --git a/tests/unit-tests/tests/models/database-test.js b/tests/unit-tests/tests/models/database-test.js index 60f4a54e3..2e8531076 100644 --- a/tests/unit-tests/tests/models/database-test.js +++ b/tests/unit-tests/tests/models/database-test.js @@ -1,7 +1,6 @@ const should = require('should'), sinon = require('sinon'), - cassandra = require('../../../../src/tests/models/database/cassandra/cassandraConnector'), sequelizeConnector = require('../../../../src/tests/models/database/sequelize/sequelizeConnector'), rewire = require('rewire'), databaseConfig = require('../../../../src/config/databaseConfig'); @@ -54,10 +53,9 @@ const functions = [ describe('Testing database', function () { let sandbox; before(function () { - process.env.DATABASE_TYPE = 'CASSANDRA'; + process.env.DATABASE_TYPE = 'SQLITE'; sandbox = sinon.sandbox.create(); functions.forEach(function (func) { - sandbox.stub(cassandra, func.functionName); sandbox.stub(sequelizeConnector, func.functionName); }); }); @@ -68,18 +66,6 @@ describe('Testing database', function () { sandbox.restore(); }); - describe('when database type is cassandra - should applied functions on cassandra client', function () { - before(async function () { - databaseConfig.type = 'cassandra'; - database = rewire('../../../../src/tests/models/database'); - }); - functions.forEach(function (func) { - it(`checking func: ${func.functionName}`, async function () { - await database[func.functionName](...func.args); - should(cassandra[func.functionName].args).eql([func.args]); - }); - }); - }); describe('when database type is not cassandra - should applied functions on sequlize client', function () { before(async function () { databaseConfig.type = 'not-cassandra'; From ebdd46adcba3928b1e93ca224040e65f632ff5cb Mon Sep 17 00:00:00 2001 From: syncush Date: Fri, 4 Sep 2020 16:34:11 +0300 Subject: [PATCH 37/38] refactor!(server): drop Cassandra support --- src/configManager/models/database/databaseConnector.js | 4 +--- src/database/database.js | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/configManager/models/database/databaseConnector.js b/src/configManager/models/database/databaseConnector.js index 62f6c96e3..b9a45e859 100644 --- a/src/configManager/models/database/databaseConnector.js +++ b/src/configManager/models/database/databaseConnector.js @@ -1,7 +1,5 @@ 'use strict'; -const sequelizeConnector = require('./sequelize/sequelizeConnector'); - -const databaseConnector = sequelizeConnector; +const databaseConnector = require('./sequelize/sequelizeConnector'); module.exports = { init, diff --git a/src/database/database.js b/src/database/database.js index 27e96acf8..62774c94c 100644 --- a/src/database/database.js +++ b/src/database/database.js @@ -1,7 +1,6 @@ 'use strict'; -let sequelizeConnector = require('./sequlize-handler/sequlize'); -let databaseConnector = sequelizeConnector; +const databaseConnector = require('./sequlize-handler/sequlize'); module.exports.init = () => { return databaseConnector.init(); From 5a635fcc2169d6308e19e0f6a3af4904e5134ba3 Mon Sep 17 00:00:00 2001 From: syncush Date: Sun, 13 Sep 2020 20:58:49 +0300 Subject: [PATCH 38/38] fix(tests): resolved comments --- .../unit-tests/jobs/models/jobManager-test.js | 4 - .../sequelize/webhooksFormatter-test.js | 233 ------------------ 2 files changed, 237 deletions(-) delete mode 100644 tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js diff --git a/tests/unit-tests/jobs/models/jobManager-test.js b/tests/unit-tests/jobs/models/jobManager-test.js index 78a8074d0..e4058222d 100644 --- a/tests/unit-tests/jobs/models/jobManager-test.js +++ b/tests/unit-tests/jobs/models/jobManager-test.js @@ -1270,8 +1270,4 @@ describe('Manager tests', function () { } }); }); - - describe('meow', function() { - - }); }); diff --git a/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js b/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js deleted file mode 100644 index 08858d21c..000000000 --- a/tests/unit-tests/webhooks/sequelize/webhooksFormatter-test.js +++ /dev/null @@ -1,233 +0,0 @@ -const sinon = require('sinon'); -const { expect } = require('chai'); -const uuid = require('uuid'); -const rewire = require('rewire'); - -const { - EVENT_FORMAT_TYPE_JSON, - EVENT_FORMAT_TYPE_SLACK, - WEBHOOK_EVENT_TYPE_STARTED, - WEBHOOK_EVENT_TYPE_FINISHED, - WEBHOOK_EVENT_TYPE_ABORTED, - WEBHOOK_EVENT_TYPE_API_FAILURE, - WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, - WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, - WEBHOOK_EVENT_TYPE_IN_PROGRESS, - WEBHOOK_EVENT_TYPE_FAILED, - WEBHOOK_EVENT_TYPES -} = require('../../../../src/common/consts'); -const webhooksFormatter = rewire('../../../../src/webhooks/models/webhooksFormatter'); - -describe('webhooksFormatter', function () { - let sandbox; - let statsFormatterStub; - before('Setup', function() { - sandbox = sinon.sandbox.create(); - statsFormatterStub = sandbox.stub(); - webhooksFormatter.__set__('statsFormatter.getStatsFormatted', statsFormatterStub); - // statsFormatterStub = sandbox.stub(webhooksFormatter, 'statsFormatter.getStatsFormatted'); - }); - afterEach(() => { - sandbox.resetHistory(); - }); - - after(() => { - sandbox.restore(); - }); - describe(EVENT_FORMAT_TYPE_SLACK, function() { - it(WEBHOOK_EVENT_TYPE_STARTED, function() { - const testId = uuid.v4(); - const jobId = uuid.v4(); - const report = { - ramp_to: 100, - test_name: 'some test name', - arrival_rate: 5, - duration: 120, - environment: 'test', - parallelism: 10 - }; - const expectedResult = `🤓 *Test ${report.test_name} with id: ${testId} has started*.\n - *test configuration:* environment: ${report.environment} duration: ${report.duration} seconds, arrival rate: ${report.arrival_rate} scenarios per second, number of runners: ${report.parallelism}, ramp to: ${report.ramp_to} scenarios per second`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_STARTED, jobId, testId, report); - - expect(payload.text).to.be.equal(expectedResult); - }); - it(WEBHOOK_EVENT_TYPE_ABORTED, function () { - const testId = uuid.v4(); - const report = { - test_name: 'some name' - }; - const expectedResult = `😢 *Test ${report.test_name} with id: ${testId} was aborted.*\n`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_ABORTED, uuid.v4(), testId, report); - - expect(payload.text).to.be.equal(expectedResult); - }); - it(WEBHOOK_EVENT_TYPE_FINISHED, function () { - const testId = uuid.v4(); - const report = { - test_name: 'some name' - }; - const additionalInfo = { - aggregatedReport: { - aggregate: { - key: 'value' - } - } - }; - const stats = 'some stats string'; - statsFormatterStub.returns(stats); - - const expectedResult = `😎 *Test ${report.test_name} with id: ${testId} is finished.*\n ${stats}\n`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_FINISHED, uuid.v4(), testId, report, additionalInfo); - - expect(payload.text).to.be.equal(expectedResult); - }); - it(WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, function () { - const testId = uuid.v4(); - const report = { - test_name: 'some name' - }; - const additionalInfo = { - lastScores: [25, 60, 12], - score: 45, - benchmarkThreshold: 90, - aggregatedReport: { - aggregate: { - key: 'value' - } - } - }; - const statsText = 'some text'; - statsFormatterStub.returns(statsText); - const expectedResult = `:cry: *Test ${report.test_name} got a score of ${additionalInfo.score.toFixed(1)}` + - ` this is below the threshold of ${additionalInfo.benchmarkThreshold}. last 3 scores are: ${additionalInfo.lastScores.join()}` + - `.*\n${statsText}\n`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_BENCHMARK_FAILED, uuid.v4(), testId, report, additionalInfo); - - expect(payload.text).to.be.equal(expectedResult); - }); - it(WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, function () { - const testId = uuid.v4(); - const report = { - test_name: 'some name' - }; - const additionalInfo = { - lastScores: [90, 100, 100], - score: 97.5, - benchmarkThreshold: 90, - aggregatedReport: { - aggregate: { - key: 'value' - } - } - }; - const statsText = 'some text'; - statsFormatterStub.returns(statsText); - const expectedResult = `:rocket: *Test ${report.test_name} got a score of ${additionalInfo.score.toFixed(1)}` + - ` this is above the threshold of ${additionalInfo.benchmarkThreshold}. last 3 scores are: ${additionalInfo.lastScores.join()}` + - `.*\n${statsText}\n`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_BENCHMARK_PASSED, uuid.v4(), testId, report, additionalInfo); - - expect(payload.text).to.be.equal(expectedResult); - }); - it(WEBHOOK_EVENT_TYPE_FAILED, function () { - const testId = uuid.v4(); - const report = { - test_name: 'some name', - environment: 'test' - }; - const additionalInfo = { - stats: { - data: 'data' - } - }; - const expectedResult = `😞 *Test with id: ${testId} Failed*.\n - test configuration:\n - environment: ${report.environment}\n - ${additionalInfo.stats.data}`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_FAILED, uuid.v4(), testId, report, additionalInfo); - - expect(payload.text).to.be.equal(expectedResult); - }); - it(WEBHOOK_EVENT_TYPE_IN_PROGRESS, function () { - const testId = uuid.v4(); - const report = { - test_name: 'some name' - }; - const expectedResult = `:hammer_and_wrench: *Test ${report.test_name} with id: ${testId} is in progress!*`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_IN_PROGRESS, uuid.v4(), testId, report); - - expect(payload.text).to.be.equal(expectedResult); - }); - it(WEBHOOK_EVENT_TYPE_API_FAILURE, function () { - const testId = uuid.v4(); - const report = { - test_name: 'some name' - }; - const expectedResult = `::boom:: *Test ${report.test_name} with id: ${testId} has encountered an API failure!* :skull:`; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_SLACK, WEBHOOK_EVENT_TYPE_API_FAILURE, uuid.v4(), testId, report); - - expect(payload.text).to.be.equal(expectedResult); - }); - it('uknown event type -> expect error to be thrown', function () { - const unknownEventType = 'superUknownEventType'; - const expectedErrorMessage = `Unrecognized webhook event: ${unknownEventType}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`; - expect(webhooksFormatter.format.bind(null, EVENT_FORMAT_TYPE_SLACK, unknownEventType)).to.throw(expectedErrorMessage); - }); - }); - describe(EVENT_FORMAT_TYPE_JSON, function () { - WEBHOOK_EVENT_TYPES.forEach(webhookEventType => { - it(webhookEventType, function () { - const testId = uuid.v4(); - const jobId = uuid.v4(); - const report = { - ramp_to: 100, - test_name: 'some test name', - arrival_rate: 5, - duration: 120, - environment: 'test', - parallelism: 10 - }; - const additionalInfo = { - some: { - nested: { - value: ['Look', 'more', 'values'] - } - } - }; - const expectedResult = { - test_id: testId, - job_id: jobId, - event: webhookEventType, - additional_details: { - report, - ...additionalInfo - } - }; - - const payload = webhooksFormatter.format(EVENT_FORMAT_TYPE_JSON, webhookEventType, jobId, testId, report, additionalInfo); - - expect(payload).to.be.deep.equal(expectedResult); - }); - }); - it('uknown event type -> expect error to be thrown', function() { - const unknownEventType = 'superUknownEventType'; - const expectedErrorMessage = `Unrecognized webhook event: ${unknownEventType}, must be one of the following: ${WEBHOOK_EVENT_TYPES.join(', ')}`; - expect(webhooksFormatter.format.bind(null, EVENT_FORMAT_TYPE_JSON, unknownEventType)).to.throw(expectedErrorMessage); - }); - }); - describe('Unknown format', function() { - it('should throw an error', function() { - const unknownFormat = 'some_random_format'; - expect(webhooksFormatter.format.bind(null, unknownFormat, WEBHOOK_EVENT_TYPE_STARTED)).to.throw(`Unrecognized webhook format: ${unknownFormat}, available options: ${[EVENT_FORMAT_TYPE_JSON, EVENT_FORMAT_TYPE_SLACK].join()}`); - }); - }); -});