diff --git a/.eslintrc b/.eslintrc index 822b61f..d71ab5a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -34,6 +34,7 @@ "@typescript-eslint/naming-convention": 0, "@typescript-eslint/dot-notation": 0, "@typescript-eslint/ban-ts-comment": 0, - "@typescript-eslint/no-var-requires": 0 + "@typescript-eslint/no-var-requires": 0, + "max-classes-per-file": 0 } } diff --git a/.github/workflows/CI-pipeline.yml b/.github/workflows/CI-pipeline.yml index eab6ff3..eaed1c6 100644 --- a/.github/workflows/CI-pipeline.yml +++ b/.github/workflows/CI-pipeline.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [10, 12, 14, 16, 18] + node: [10, 12, 14, 16, 18, 20] steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 128e75f..5b96e13 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,13 +22,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: 18 - - name: Clean install of node dependencies - run: npm ci + - name: Install of node dependencies + run: npm install - name: Run lint run: npm run lint - name: Run tests @@ -41,14 +41,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: 18 registry-url: 'https://registry.npmjs.org' - - name: Clean install of node dependencies - run: npm ci + - name: Install of node dependencies + run: npm install - name: Publish to NPM run: | npm config set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN @@ -57,7 +57,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} - name: Set up Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: 18 registry-url: 'https://npm.pkg.github.com' diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e9598..150efad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +### Changed +- `token` configuration option was renamed to `apiKey` to maintain common convention. ## [5.0.11] - 2023-06-01 ### Fixed diff --git a/README.md b/README.md index e9ef28f..9524125 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # ReportPortal js client + This Client is to communicate with the Report Portal on Node.js. Library is used only for implementors of custom listeners for ReportPortal. ## Already implemented listeners: + * [Jest integration](https://github.com/reportportal/agent-js-jest) * [Cypress integration](https://github.com/reportportal/agent-js-cypress) * [Mocha integration](https://github.com/reportportal/agent-js-mocha) @@ -19,21 +21,22 @@ Library is used only for implementors of custom listeners for ReportPortal. Examples for test framework integrations from the list above described in [examples](https://github.com/reportportal/examples-js) repository. ## Installation + The latest version is available on npm: ```cmd npm install @reportportal/client-javascript ``` -## Example +## Usage example ```javascript const RPClient = require('@reportportal/client-javascript'); const rpClient = new RPClient({ - token: "00000000-0000-0000-0000-000000000000", - endpoint: "http://your-instance.com:8080/api/v1", - launch: "LAUNCH_NAME", - project: "PROJECT_NAME" + apiKey: 'reportportalApiKey', + endpoint: 'http://your-instance.com:8080/api/v1', + launch: 'LAUNCH_NAME', + project: 'PROJECT_NAME' }); rpClient.checkConnect().then((response) => { @@ -45,25 +48,29 @@ rpClient.checkConnect().then((response) => { }); ``` -## Settings -When creating a client instance, you need to specify the following parameters: +## Configuration + +When creating a client instance, you need to specify the following options: -| Parameter | Description | -|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| token | User's token Report Portal from which you want to send requests. It can be found on the profile page of this user. | -| endpoint | URL of your server. For example, if you visit the page at 'https://server:8080/ui', then endpoint will be equal to 'https://server:8080/api/v1'. | -| launch | Name of launch at creation. | -| project | The name of the project in which the launches will be created. | -| headers | (optional) The object with custom headers for internal http client. | -| debug | (optional) *Default: false.* This flag allows seeing the logs of the client. Useful for debugging. | -| isLaunchMergeRequired | (optional) *Default: false.* Allows client to merge launches into one at the end of the run via saving their UUIDs to the file system. At the end of the run launches can be merged using `mergeLaunches` method. | -| restClientConfig | (optional) The object with `agent` property for configure [http(s)](https://nodejs.org/api/https.html#https_https_request_url_options_callback) client, may contain other client options eg. `timeout`. | +| Option | Necessity | Default | Description | +|-----------------------|------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| apiKey | Required | | User's reportportal token from which you want to send requests. It can be found on the profile page of this user. | +| endpoint | Required | | URL of your server. For example, if you visit the page at 'https://server:8080/ui', then endpoint will be equal to 'https://server:8080/api/v1'. | +| launch | Required | | Name of launch at creation. | +| project | Required | | The name of the project in which the launches will be created. | +| headers | Optional | {} | The object with custom headers for internal http client. | +| debug | Optional | false | This flag allows seeing the logs of the client. Useful for debugging. | +| isLaunchMergeRequired | Optional | false | Allows client to merge launches into one at the end of the run via saving their UUIDs to the file system. At the end of the run launches can be merged using `mergeLaunches` method. | +| restClientConfig | Optional | Not set | The object with `agent` property for configure [http(s)](https://nodejs.org/api/https.html#https_https_request_url_options_callback) client, may contain other client options eg. `timeout`. | +| token | Deprecated | Not set | Use `apiKey` instead. | ## Asynchronous reporting + The client supports an asynchronous reporting. -If you want the client to work asynchronously change v1 to v2 in addresses in endpoint. +If you want the client to work asynchronously change `v1` to `v2` in addresses in endpoint. + +## API -## Api Each method (except checkConnect) returns an object in a specific format: ```javascript { @@ -74,12 +81,15 @@ Each method (except checkConnect) returns an object in a specific format: The client works synchronously, so it is not necessary to wait for the end of the previous requests to send following ones. ### Timeout (30000ms) on axios requests -There is a timeout on axios requests. If for instance the server your making a request to is taking too long to load, then axios timeout will work and you will see the error "Error: timeout of 30000ms exceeded". + +There is a timeout on axios requests. If for instance the server your making a request to is taking too long to load, then axios timeout will work and you will see the error 'Error: timeout of 30000ms exceeded'. You can simply change this timeout by adding a `timeout` property to `restClientConfig` with your desired numeric value (in _ms_) or *0* to disable it. ### checkConnect - checkConnect - asynchronous method for verifying the correctness of the client connection + + `checkConnect` - asynchronous method for verifying the correctness of the client connection + ```javascript rpClient.checkConnect().then((response) => { console.log('You have successfully connected to the server.'); @@ -91,19 +101,21 @@ rpClient.checkConnect().then((response) => { ``` ### startLaunch -startLaunch - starts a new launch, return temp id that you want to use for all of the items within this launch. + +`startLaunch` - starts a new launch, return temp id that you want to use for the all items within this launch. + ```javascript const launchObj = rpClient.startLaunch({ - name: "Client test", + name: 'Client test', startTime: rpClient.helpers.now(), - description: "description of the launch", + description: 'description of the launch', attributes: [ { - "key": "yourKey", - "value": "yourValue" + 'key': 'yourKey', + 'value': 'yourValue' }, { - "value": "yourValue" + 'value': 'yourValue' } ], //this param used only when you need client to send data into the existing launch @@ -114,16 +126,17 @@ console.log(launchObj.tempId); The method takes one argument: * launch data object: -Parameter | Description ---------- | ----------- -startTime | (optional) start time launch(unix time). Default: rpClient.helpers.now() -name | (optional) launch name. Default: parameter 'launch' specified when creating the client instance -mode | (optional) "DEFAULT" or "DEBUG". Default: "DEFAULT" -description | (optional) description of the launch (supports markdown syntax) -attributes | (optional) array of launch tags -id | id of the existing launch in which tests data would be sent, without this param new launch instance would be created +| Option | Necessity | Default | Description | +|-------------|-----------|----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| startTime | Optional | rpClient.helpers.now() | (optional) start time launch(unix time). | +| name | Optional | parameter 'launch' specified when creating the client instance | Name of the launch. | +| mode | Optional | 'DEFAULT' | 'DEFAULT' - results will be submitted to Launches page, 'DEBUG' - results will be submitted to Debug page. | +| description | Optional | '' | Description of the launch (supports markdown syntax). | +| attributes | Optional | [] | Array of launch attributes (tags). | +| id | Optional | Not set | `ID` of the existing launch in which tests data would be sent, without this param new launch instance will be created. | + +To get the real launch `ID` (also known as launch `UUID` from database) wait for the returned promise to finish. -To know the real launch id wait for the method to finish. The real id is used by the client in asynchronous reporting. ```javascript const launchObj = rpClient.startLaunch(); launchObj.promise.then((response) => { @@ -149,49 +162,57 @@ If you still want to switch Off Google Analytics, please change env variable. 'REPORTPORTAL_CLIENT_JS_NO_ANALYTICS=true' ### finishLaunch -finishLaunch - finish of the launch. After calling this method, you can not add items to the launch. + +`finishLaunch` - finish of the launch. After calling this method, you can not add items to the launch. The request to finish the launch will be sent only after all items within it have finished. + ```javascript // launchObj - object returned by method 'startLaunch' const launchFinishObj = rpClient.finishLaunch(launchObj.tempId, { endTime: rpClient.helpers.now() }); ``` + The method takes two arguments: -* id launch (returned by method 'startLaunch') +* launch `tempId` (returned by the method `startLaunch`) * data object: -Parameter | Description ---------- | ----------- -endTime | (optional) end time of launch. Default: rpClient.helpers.now() -status | (optional) status of launch, one of "", "PASSED", "FAILED", "STOPPED", "SKIPPED", "INTERRUPTED", "CANCELLED". +|Option | Necessity | Default | Description | +|--------- |-----------|---------|---------------------------------------------------------------------------------------------------| +|endTime | Optional | rpClient.helpers.now() | End time of the launch. | +|status | Optional | '' | Status of launch, one of '', 'PASSED', 'FAILED', 'STOPPED', 'SKIPPED', 'INTERRUPTED', 'CANCELLED'. | ### getPromiseFinishAllItems -getPromiseFinishAllItems - returns promise that contains status about all data has been sent to the Report Protal. -This method needed when test frameworks don't wait for async methods and stop processed. + +`getPromiseFinishAllItems` - returns promise that contains status about all data has been sent to the reportportal. +This method needed when test frameworks don't wait for async methods until finished. ```javascript -// jasmine example. tempLaunchId - id of the client's process -agent.getPromiseFinishAllItems(agent.tempLaunchId).then(()=> done()); +// jasmine example. tempLaunchId - tempId of the launch started by the current client process +agent.getPromiseFinishAllItems(agent.tempLaunchId).then(() => done()); ``` -Parameter | Description ---------- | ----------- -tempLaunchId | id of the client's process + +| Option | Necessity | Default | Description | +|--------------|-----------|---------|----------------------------| +| tempLaunchId | Required | | `tempId` of the launch started by the current client process | ### updateLaunch -updateLaunch - updates launch data. Will send a request to the server only after finishing the launch. + +`updateLaunch` - updates the launch data. Will send a request to the server only after finishing the launch. + ```javascript // launchObj - object returned by method 'startLaunch' rpClient.updateLaunch( - launchObj.tempId, { + launchObj.tempId, + { description: 'new launch description', attributes: [ { - "key": "yourKey", - "value": "yourValue" + key: 'yourKey', + value: 'yourValue' }, { - "value": "yourValue" + value: 'yourValue' } ], mode: 'DEBUG' @@ -199,18 +220,20 @@ rpClient.updateLaunch( ); ``` The method takes two arguments: -* id launch (returned by method 'startLaunch') -* data object - may contain the following fields: description, tags, mode. These fields can be looked up in the method "startLaunch". +* launch `tempId` (returned by the method 'startLaunch') +* data object - may contain the following fields: `description`, `attributes`, `mode`. These fields can be looked up in the method `startLaunch`. ### startTestItem -startTestItem - starts a new test item. + +`startTestItem` - starts a new test item. + ```javascript // launchObj - object returned by method 'startLaunch' const suiteObj = rpClient.startTestItem({ description: makeid(), name: makeid(), startTime: rpClient.helpers.now(), - type: "SUITE" + type: 'SUITE' }, launchObj.tempId); const stepObj = rpClient.startTestItem({ description: makeid(), @@ -219,97 +242,101 @@ const stepObj = rpClient.startTestItem({ attributes: [ { - "key": "yourKey", - "value": "yourValue" + key: 'yourKey', + value: 'yourValue' }, { - "value": "yourValue" + value: 'yourValue' } ], - type: "STEP" + type: 'STEP' }, launchObj.tempId, suiteObj.tempId); ``` The method takes three arguments: * test item data object: -Parameter | Description ---------- | ----------- -name | item name -type | Item type, one of 'SUITE', 'STORY', 'TEST', 'SCENARIO', 'STEP', 'BEFORE_CLASS', 'BEFORE_GROUPS','BEFORE_METHOD', 'BEFORE_SUITE', 'BEFORE_TEST', 'AFTER_CLASS', 'AFTER_GROUPS', 'AFTER_METHOD', 'AFTER_SUITE', 'AFTER_TEST' -hasStats | (optional) Changes behavior for item of type 'STEP'. When set to true, step is treaten as a test case (entity containig statistics). When false, step becomes a nested step. Default: true -description | (optional) description of the launch (supports markdown syntax) -startTime | (optional) start time item(unix time). Default: rpClient.helpers.now() -attributes | (optional) array of item attributes - -* id launch (returned by method 'startLaunch') -* id parent item (optional) (returned by method 'startTestItem') +|Option | Necessity | Default | Description | +|--------- |-----------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|name | Required | | Test item name | +|type | Required | | Test item type, one of 'SUITE', 'STORY', 'TEST', 'SCENARIO', 'STEP', 'BEFORE_CLASS', 'BEFORE_GROUPS','BEFORE_METHOD', 'BEFORE_SUITE', 'BEFORE_TEST', 'AFTER_CLASS', 'AFTER_GROUPS', 'AFTER_METHOD', 'AFTER_SUITE', 'AFTER_TEST' | +|hasStats | Optional | true | Changes behavior for test item of type 'STEP'. When set to `true`, step is treaten as a test case (entity containig statistics). When false, step becomes a nested step. | +|description | Optional | '' | Description of the test item (supports markdown syntax). | +|startTime | Optional | rpClient.helpers.now() | Start time of the test item (unix time). | +|attributes | Optional | [] | Array of the test item attributes. | +* launch `tempId` (returned by the method `startLaunch`) +* parent test item `tempId` (*optional*) (returned by method `startTestItem`) ### finishTestItem -finishTestItem - finish of the item. After calling this method, you can not add items to the item. -The request to finish the item will be sent only after all items within it have finished. + +`finishTestItem` - finish of the test item. After calling this method, you can not add items to the test item. +The request to finish the test item will be sent only after all test items within it have finished. + ```javascript // itemObj - object returned by method 'startTestItem' rpClient.finishTestItem(itemObj.tempId, { - status: "failed" + status: 'failed' }) ``` The method takes two arguments: -* id item (returned by method 'startTestItem') +* test item `tempId` (returned by the method `startTestItem`) * data object: -Parameter | Description ---------- | ----------- -endTime | (optional) end time of launch. Default: rpClient.helpers.now() -status | (optional) item status, one of "", "PASSED", "FAILED", "STOPPED", "SKIPPED", "INTERRUPTED", "CANCELLED". Default: "PASSED". -issue | (optional) object issue. IssueType is required, allowable values: "pb***", "ab***", "si***", "ti***", "nd001". Where *** is locator id +| Option | Necessity | Default | Description | +|---------|-----------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| issue | Optional | true | Test item issue object. `issueType` is required, allowable values: 'pb***', 'ab***', 'si***', 'ti***', 'nd001'. Where `***` is locator id | +| status | Optional | 'PASSED' | Test item status, one of '', 'PASSED', 'FAILED', 'STOPPED', 'SKIPPED', 'INTERRUPTED', 'CANCELLED'. | +| endTime | Optional | rpClient.helpers.now() | End time of the launch (unix time). | Example issue object: ``` { - issueType: "string", - comment: "string", + issueType: 'string', + comment: 'string', externalSystemIssues: [ { submitDate: 0, - submitter: "string", - systemId: "string", - ticketId: "string", - url: "string" + submitter: 'string', + systemId: 'string', + ticketId: 'string', + url: 'string' } ] } ``` ### sendLog -sendLog - adds a log to the item + +`sendLog` - adds a log to the test item. + ```javascript // stepObj - object returned by method 'startTestItem' rpClient.sendLog(stepObj.tempId, { - level: "INFO", - message: makeid(), + level: 'INFO', + message: 'User clicks login button', time: rpClient.helpers.now() }) ``` The method takes three arguments: -* id item (returned by method 'startTestItem') +* test item `tempId` (returned by method `startTestItem`) * data object: -Parameter | Description ---------- | ----------- -time | (optional) time of log. Default: rpClient.helpers.now() -message | (optional) log message. Default: ''. -status | (optional) log status, one of 'trace', 'debug', 'info', 'warn', 'error', ''. Default "". +| Option | Necessity | Default | Description | +|---------|-----------|------------------------|----------------------------------------------------------------------| +| message | Optional | '' | The log message. | +| level | Optional | '' | The log level, one of 'trace', 'debug', 'info', 'warn', 'error', ''. | +| time | Optional | rpClient.helpers.now() | The time of the log. | * file object (optional): -Parameter | Description ---------- | ----------- -name | file name -type | file mimeType, example "image/png" (support types: 'image/*', application/ ['xml', 'javascript', 'json', 'css', 'php'] , another format will be opened in a new browser tab ), -content | file +| Option | Necessity | Default | Description | +|---------|-----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| name | Required | | The name of the file. | +| type | Required | | The file mimeType, example 'image/png' (support types: 'image/*', application/['xml', 'javascript', 'json', 'css', 'php'], other formats will be opened in reportportal in a new browser tab only). | +| content | Required | | base64 encoded file content. | # Copyright Notice + Licensed under the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) license (see the LICENSE.txt file). diff --git a/VERSION b/VERSION index 1757758..d015685 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.0.11 +5.0.12-SNAPSHOT diff --git a/lib/commons/config.js b/lib/commons/config.js new file mode 100644 index 0000000..d41ad45 --- /dev/null +++ b/lib/commons/config.js @@ -0,0 +1,61 @@ +const { ReportPortalRequiredOptionError, ReportPortalValidationError } = require('./errors'); + +const getRequiredOption = (options, optionName) => { + if (!Object.prototype.hasOwnProperty.call(options, optionName) || !options[optionName]) { + throw new ReportPortalRequiredOptionError(optionName); + } + + return options[optionName]; +}; + +const getApiKey = ({ apiKey, token }) => { + let calculatedApiKey = apiKey; + if (!calculatedApiKey) { + calculatedApiKey = token; + if (!calculatedApiKey) { + throw new ReportPortalRequiredOptionError('apiKey'); + } else { + console.warn(`Option 'token' is deprecated. Use 'apiKey' instead.`); + } + } + + return calculatedApiKey; +}; + +const getClientConfig = (options) => { + let calculatedOptions = options; + try { + if (typeof options !== 'object') { + throw new ReportPortalValidationError('`options` must be an object.'); + } + const apiKey = getApiKey(options); + const project = getRequiredOption(options, 'project'); + const endpoint = getRequiredOption(options, 'endpoint'); + + calculatedOptions = { + apiKey, + project, + endpoint, + launch: options.launch, + debug: options.debug, + isLaunchMergeRequired: + options.isLaunchMergeRequired === undefined ? false : options.isLaunchMergeRequired, + headers: options.headers, + restClientConfig: options.restClientConfig, + attributes: options.attributes, + mode: options.mode, + description: options.description, + }; + } catch (error) { + // don't throw the error up to not break the entire process + console.dir(error); + } + + return calculatedOptions; +}; + +module.exports = { + getClientConfig, + getRequiredOption, + getApiKey, +}; diff --git a/lib/commons/errors.js b/lib/commons/errors.js new file mode 100644 index 0000000..a47a6a3 --- /dev/null +++ b/lib/commons/errors.js @@ -0,0 +1,29 @@ +class ReportPortalError extends Error { + constructor(message) { + const basicMessage = `\nReportPortal client error: ${message}`; + super(basicMessage); + this.name = 'ReportPortalError'; + } +} + +class ReportPortalValidationError extends ReportPortalError { + constructor(message) { + const basicMessage = `\nValidation failed. Please, check the specified parameters: ${message}`; + super(basicMessage); + this.name = 'ReportPortalValidationError'; + } +} + +class ReportPortalRequiredOptionError extends ReportPortalValidationError { + constructor(propertyName) { + const basicMessage = `\nProperty '${propertyName}' must not be empty.`; + super(basicMessage); + this.name = 'ReportPortalRequiredOptionError'; + } +} + +module.exports = { + ReportPortalError, + ReportPortalValidationError, + ReportPortalRequiredOptionError, +}; diff --git a/lib/report-portal-client.js b/lib/report-portal-client.js index 216ab67..bd7ffca 100644 --- a/lib/report-portal-client.js +++ b/lib/report-portal-client.js @@ -3,6 +3,7 @@ const UniqId = require('uniqid'); const { URLSearchParams } = require('url'); const helpers = require('./helpers'); const RestClient = require('./rest'); +const { getClientConfig } = require('./commons/config'); const Statistics = require('../statistics/statistics'); const { EVENT_NAME } = require('../statistics/constants'); const { RP_STATUSES } = require('./constants/statuses'); @@ -12,10 +13,10 @@ const MULTIPART_BOUNDARY = Math.floor(Math.random() * 10000000000).toString(); class RPClient { /** * Create a client for RP. - * @param {Object} params - config object. - * params should look like this + * @param {Object} options - config object. + * options should look like this * { - * token: "00000000-0000-0000-0000-000000000000", + * apiKey: "reportportalApiKey", * endpoint: "http://localhost:8080/api/v1", * launch: "YOUR LAUNCH NAME", * project: "PROJECT NAME", @@ -28,24 +29,26 @@ class RPClient { * version: "AGENT VERSION", * } */ - constructor(params, agentParams) { - this.debug = params.debug; - this.isLaunchMergeRequired = - params.isLaunchMergeRequired === undefined ? false : params.isLaunchMergeRequired; + constructor(options, agentParams) { + this.config = getClientConfig(options); + this.debug = this.config.debug; + this.isLaunchMergeRequired = this.config.isLaunchMergeRequired; + this.apiKey = this.config.apiKey; + // deprecated + this.token = this.apiKey; + this.map = {}; - this.baseURL = [params.endpoint, params.project].join('/'); + this.baseURL = [this.config.endpoint, this.config.project].join('/'); this.headers = { 'User-Agent': 'NodeJS', - Authorization: `bearer ${params.token}`, - ...(params.headers || {}), + Authorization: `bearer ${this.apiKey}`, + ...(this.config.headers || {}), }; - this.token = params.token; - this.config = params; this.helpers = helpers; this.restClient = new RestClient({ baseURL: this.baseURL, headers: this.headers, - restClientConfig: params.restClientConfig, + restClientConfig: this.config.restClientConfig, }); this.statistics = new Statistics(EVENT_NAME, agentParams); this.launchUuid = ''; diff --git a/spec/config.spec.js b/spec/config.spec.js new file mode 100644 index 0000000..bbd0081 --- /dev/null +++ b/spec/config.spec.js @@ -0,0 +1,116 @@ +const { getClientConfig, getRequiredOption, getApiKey } = require('../lib/commons/config'); +const { + ReportPortalRequiredOptionError, + ReportPortalValidationError, +} = require('../lib/commons/errors'); + +describe('Config commons test suite', () => { + describe('getRequiredOption', () => { + it('should return option if it presented in options and has value', () => { + const option = getRequiredOption({ project: 1 }, 'project'); + + expect(option).toBe(1); + }); + + it('should throw ReportPortalRequiredOptionError in case of empty option', () => { + let error; + try { + getRequiredOption({ project: undefined }, 'project'); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(ReportPortalRequiredOptionError); + }); + + it( + 'should throw ReportPortalRequiredOptionError in case of option ' + 'not present in options', + () => { + let error; + try { + getRequiredOption({ other: 1 }, 'project'); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(ReportPortalRequiredOptionError); + }, + ); + }); + + describe('getApiKey', () => { + it('should return apiKey if it presented in options and has value', () => { + const apiKey = getApiKey({ apiKey: '1' }); + + expect(apiKey).toBe('1'); + }); + + it('should return apiKey if it provided in options via deprecated token option', () => { + const apiKey = getApiKey({ token: '1' }); + + expect(apiKey).toBe('1'); + }); + + it('should print warning to console if deprecated token option used', () => { + spyOn(console, 'warn'); + + getApiKey({ token: '1' }); + + expect(console.warn).toHaveBeenCalledWith( + `Option 'token' is deprecated. Use 'apiKey' instead.`, + ); + }); + + it('should throw ReportPortalRequiredOptionError in case of no one option present', () => { + let error; + try { + getApiKey({}); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(ReportPortalRequiredOptionError); + }); + }); + + describe('getClientConfig', () => { + it('should print ReportPortalValidationError error to the console in case of options is not an object type', () => { + spyOn(console, 'dir'); + getClientConfig('options'); + + expect(console.dir).toHaveBeenCalledWith( + new ReportPortalValidationError('`options` must be an object.'), + ); + }); + + it('should print ReportPortalRequiredOptionError to the console in case of "endpoint" option missed', () => { + spyOn(console, 'dir'); + getClientConfig({ + apiKey: '123', + project: 'prj', + }); + + expect(console.dir).toHaveBeenCalledWith(new ReportPortalRequiredOptionError('endpoint')); + }); + + it('should print ReportPortalRequiredOptionError to the console in case of "project" option missed', () => { + spyOn(console, 'dir'); + getClientConfig({ + apiKey: '123', + endpoint: 'https://abc.com', + }); + + expect(console.dir).toHaveBeenCalledWith(new ReportPortalRequiredOptionError('project')); + }); + + it('should print ReportPortalRequiredOptionError to the console in case of "apiKey" option missed', () => { + spyOn(console, 'dir'); + getClientConfig({ + project: 'prj', + endpoint: 'https://abc.com', + }); + + expect(console.dir).toHaveBeenCalledWith(new ReportPortalRequiredOptionError('apiKey')); + }); + }); +}); diff --git a/spec/helper.spec.js b/spec/helpers.spec.js similarity index 77% rename from spec/helper.spec.js rename to spec/helpers.spec.js index ff16f3c..52c4d58 100644 --- a/spec/helper.spec.js +++ b/spec/helpers.spec.js @@ -1,31 +1,30 @@ const os = require('os'); const fs = require('fs'); const glob = require('glob'); -const RPClient = require('../lib/report-portal-client'); +const helpers = require('../lib/helpers'); const RestClient = require('../lib/rest'); const pjson = require('../package.json'); describe('Helpers', () => { - const client = new RPClient({ token: 'token' }); describe('formatName', () => { it('slice last 256 symbols', () => { - expect(client.helpers.formatName(`a${'b'.repeat(256)}`)).toBe('b'.repeat(256)); + expect(helpers.formatName(`a${'b'.repeat(256)}`)).toBe('b'.repeat(256)); }); it('leave 256 symbol name as is', () => { - expect(client.helpers.formatName('c'.repeat(256))).toBe('c'.repeat(256)); + expect(helpers.formatName('c'.repeat(256))).toBe('c'.repeat(256)); }); it('leave 3 symbol name as is', () => { - expect(client.helpers.formatName('abc')).toBe('abc'); + expect(helpers.formatName('abc')).toBe('abc'); }); it('complete with dots 2 symbol name', () => { - expect(client.helpers.formatName('ab')).toBe('ab.'); + expect(helpers.formatName('ab')).toBe('ab.'); }); }); describe('now', () => { it('returns milliseconds from unix time', () => { - expect(new Date() - client.helpers.now()).toBeLessThan(100); // less than 100 miliseconds difference + expect(new Date() - helpers.now()).toBeLessThan(100); // less than 100 miliseconds difference }); }); @@ -33,7 +32,7 @@ describe('Helpers', () => { it('calls RestClient#request', () => { spyOn(RestClient, 'request'); - client.helpers.getServerResult( + helpers.getServerResult( 'http://localhost:80/api/v1', { userId: 1 }, { @@ -61,7 +60,7 @@ describe('Helpers', () => { it('should return the right ids', () => { spyOn(glob, 'sync').and.returnValue(['rplaunch-fileOne.tmp', 'rplaunch-fileTwo.tmp']); - const ids = client.helpers.readLaunchesFromFile(); + const ids = helpers.readLaunchesFromFile(); expect(ids).toEqual(['fileOne', 'fileTwo']); }); @@ -71,7 +70,7 @@ describe('Helpers', () => { it('should call fs.open method with right parameters', () => { spyOn(fs, 'open'); - client.helpers.saveLaunchIdToFile('fileOne'); + helpers.saveLaunchIdToFile('fileOne'); expect(fs.open).toHaveBeenCalledWith('rplaunch-fileOne.tmp', 'w', jasmine.any(Function)); }); @@ -106,7 +105,7 @@ describe('Helpers', () => { }, ]; - const attr = client.helpers.getSystemAttribute(); + const attr = helpers.getSystemAttribute(); expect(attr).toEqual(expectedAttr); }); @@ -114,13 +113,13 @@ describe('Helpers', () => { describe('generateTestCaseId', () => { it('should return undefined if there is no codeRef', () => { - const testCaseId = client.helpers.generateTestCaseId(); + const testCaseId = helpers.generateTestCaseId(); expect(testCaseId).toEqual(undefined); }); it('should return codeRef if there is no params', () => { - const testCaseId = client.helpers.generateTestCaseId('codeRef'); + const testCaseId = helpers.generateTestCaseId('codeRef'); expect(testCaseId).toEqual('codeRef'); }); @@ -139,7 +138,7 @@ describe('Helpers', () => { }, ]; - const testCaseId = client.helpers.generateTestCaseId('codeRef', parameters); + const testCaseId = helpers.generateTestCaseId('codeRef', parameters); expect(testCaseId).toEqual('codeRef[value,valueTwo,valueThree]'); }); diff --git a/spec/report-portal-client.spec.js b/spec/report-portal-client.spec.js index 284abdd..2a41e0c 100644 --- a/spec/report-portal-client.spec.js +++ b/spec/report-portal-client.spec.js @@ -5,16 +5,27 @@ const helpers = require('../lib/helpers'); describe('ReportPortal javascript client', () => { describe('constructor', () => { - it('executes without error', () => { - const client = new RPClient({ token: 'test' }); + it('creates the client instance without error', () => { + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + }); - expect(client.config.token).toBe('test'); + expect(client.config.apiKey).toBe('test'); + expect(client.config.project).toBe('test'); + expect(client.config.endpoint).toBe('https://abc.com'); }); }); describe('logDebug', () => { it('should call console.log with provided message if debug is true', () => { - const client = new RPClient({ debug: true }); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + debug: true, + }); spyOn(console, 'log'); client.logDebug('message'); @@ -23,7 +34,12 @@ describe('ReportPortal javascript client', () => { }); it('should call console.log with messages if debug is true and dataMsg provided', () => { - const client = new RPClient({ debug: true }); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + debug: true, + }); spyOn(console, 'log'); client.logDebug('message', { key: 1, value: 2 }); @@ -32,7 +48,12 @@ describe('ReportPortal javascript client', () => { }); it('should not call console.log if debug is false', () => { - const client = new RPClient({ debug: false }); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + debug: false, + }); spyOn(console, 'log'); client.logDebug('message'); @@ -43,7 +64,11 @@ describe('ReportPortal javascript client', () => { describe('calculateNonRetriedItemMapKey', () => { it("should return correct parameter's string", () => { - const client = new RPClient({}); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + }); const str = client.calculateNonRetriedItemMapKey('lId', 'pId', 'name', 'itemId'); @@ -51,7 +76,11 @@ describe('ReportPortal javascript client', () => { }); it("should return correct parameter's string with default value if itemId doesn't pass", () => { - const client = new RPClient({}); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + }); const str = client.calculateNonRetriedItemMapKey('lId', 'pId', 'name'); @@ -61,7 +90,11 @@ describe('ReportPortal javascript client', () => { describe('getRejectAnswer', () => { it('should return object with tempId and promise.reject with error', () => { - const client = new RPClient({}); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + }); const rejectAnswer = client.getRejectAnswer('tempId', 'error'); @@ -72,7 +105,11 @@ describe('ReportPortal javascript client', () => { describe('cleanMap', () => { it('should delete element with id', () => { - const client = new RPClient({}); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + }); client.map = { id1: 'firstElement', id2: 'secondElement', @@ -87,7 +124,11 @@ describe('ReportPortal javascript client', () => { describe('checkConnect', () => { it('should return promise', () => { - const client = new RPClient({ endpoint: 'endpoint' }); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + }); spyOn(RestClient, 'request').and.returnValue(Promise.resolve('ok')); const request = client.checkConnect(); @@ -109,7 +150,7 @@ describe('ReportPortal javascript client', () => { it('should call statistics.trackEvent if REPORTPORTAL_CLIENT_JS_NO_ANALYTICS is not set', () => { const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', }); @@ -122,7 +163,7 @@ describe('ReportPortal javascript client', () => { it('should not call statistics.trackEvent if REPORTPORTAL_CLIENT_JS_NO_ANALYTICS is true', () => { const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', }); @@ -141,7 +182,7 @@ describe('ReportPortal javascript client', () => { }; const client = new RPClient( { - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', }, @@ -160,7 +201,7 @@ describe('ReportPortal javascript client', () => { it('should create statistics object without agentParams if they are empty', () => { const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', }); @@ -190,7 +231,7 @@ describe('ReportPortal javascript client', () => { }, ]; const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', }); @@ -223,7 +264,7 @@ describe('ReportPortal javascript client', () => { }, ]; const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', }); @@ -258,7 +299,7 @@ describe('ReportPortal javascript client', () => { it('dont start new launch if launchDataRQ.id is not empty', () => { const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', }); @@ -279,7 +320,7 @@ describe('ReportPortal javascript client', () => { describe('finishLaunch', () => { it('should call getRejectAnswer if there is no launchTempId with suitable launchTempId', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { id1: { children: ['child1'], @@ -296,7 +337,7 @@ describe('ReportPortal javascript client', () => { }); it('should trigger promiseFinish', (done) => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { id1: { children: ['child1'], @@ -327,7 +368,12 @@ describe('ReportPortal javascript client', () => { name: 'Test launch name', attributes: [{ value: 'value' }], }; - const client = new RPClient({ attributes: [{ value: 'value' }] }); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + attributes: [{ value: 'value' }], + }); spyOn(client.helpers, 'now').and.returnValue(12345734); const mergeLaunches = client.getMergeLaunchesRequest(['12345', '12346']); @@ -346,7 +392,13 @@ describe('ReportPortal javascript client', () => { name: 'launch', attributes: [{ value: 'value' }], }; - const client = new RPClient({ launch: 'launch', attributes: [{ value: 'value' }] }); + const client = new RPClient({ + apiKey: 'test', + project: 'test', + endpoint: 'https://abc.com', + launch: 'launch', + attributes: [{ value: 'value' }], + }); spyOn(client.helpers, 'now').and.returnValue(12345734); const mergeLaunches = client.getMergeLaunchesRequest(['12345', '12346']); @@ -370,7 +422,7 @@ describe('ReportPortal javascript client', () => { it('should calls client', (done) => { const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', isLaunchMergeRequired: true, @@ -400,6 +452,9 @@ describe('ReportPortal javascript client', () => { it('should not call client if something went wrong', (done) => { const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', isLaunchMergeRequired: true, }); @@ -418,7 +473,7 @@ describe('ReportPortal javascript client', () => { it('should return undefined if isLaunchMergeRequired is false', () => { const client = new RPClient({ - token: 'startLaunchTest', + apiKey: 'startLaunchTest', endpoint: 'https://rp.us/api/v1', project: 'tst', isLaunchMergeRequired: false, @@ -432,7 +487,11 @@ describe('ReportPortal javascript client', () => { describe('getPromiseFinishAllItems', () => { it('should return promise', (done) => { - const client = new RPClient({}); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id1: { children: ['child1'], @@ -452,7 +511,11 @@ describe('ReportPortal javascript client', () => { describe('updateLaunch', () => { it('should call getRejectAnswer if there is no launchTempId with suitable launchTempId', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id1: { children: ['child1'], @@ -469,7 +532,11 @@ describe('ReportPortal javascript client', () => { }); it('should return object with tempId and promise', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id1: { children: ['child1'], @@ -487,7 +554,11 @@ describe('ReportPortal javascript client', () => { describe('startTestItem', () => { it('should call getRejectAnswer if there is no launchTempId with suitable launchTempId', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id1: { children: ['child1'], @@ -504,7 +575,11 @@ describe('ReportPortal javascript client', () => { }); it('should call getRejectAnswer if launchObj.finishSend is true', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id1: { children: ['child1'], @@ -522,7 +597,11 @@ describe('ReportPortal javascript client', () => { }); it('should call getRejectAnswer if there is no parentObj with suitable parentTempId', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id: { children: ['id1'], @@ -540,7 +619,11 @@ describe('ReportPortal javascript client', () => { }); it('should return object with tempId and promise', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id: { children: ['id1', '4n5pxq24kpiob12og9'], @@ -565,7 +648,11 @@ describe('ReportPortal javascript client', () => { }); it('should call nonRetriedItemMap if retry is false', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id: { children: ['id1', '4n5pxq24kpiob12og9'], @@ -595,7 +682,11 @@ describe('ReportPortal javascript client', () => { describe('finishTestItem', () => { it('should call getRejectAnswer if there is no itemObj with suitable itemTempId', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id1: { children: ['child1'], @@ -612,7 +703,11 @@ describe('ReportPortal javascript client', () => { }); it('should call finishTestItemPromiseStart with correct parameters', (done) => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id: { children: ['id1'], @@ -642,7 +737,11 @@ describe('ReportPortal javascript client', () => { }); it('should call finishTestItemPromiseStart with correct parameters if smt went wrong', (done) => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ + apiKey: 'startLaunchTest', + endpoint: 'https://rp.us/api/v1', + project: 'tst', + }); client.map = { id: { children: ['id1'], @@ -674,7 +773,7 @@ describe('ReportPortal javascript client', () => { describe('saveLog', () => { it('should return object with tempId and promise', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { id1: { children: ['child1'], @@ -699,7 +798,7 @@ describe('ReportPortal javascript client', () => { describe('sendLog', () => { it('should return sendLogWithFile if fileObj is not empty', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); spyOn(client, 'sendLogWithFile').and.returnValue('sendLogWithFile'); const result = client.sendLog('itemTempId', { message: 'message' }, { name: 'name' }); @@ -708,7 +807,7 @@ describe('ReportPortal javascript client', () => { }); it('should return sendLogWithoutFile if fileObj is empty', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); spyOn(client, 'sendLogWithoutFile').and.returnValue('sendLogWithoutFile'); const result = client.sendLog('itemTempId', { message: 'message' }); @@ -719,7 +818,7 @@ describe('ReportPortal javascript client', () => { describe('sendLogWithoutFile', () => { it('should call getRejectAnswer if there is no itemObj with suitable itemTempId', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { id1: { children: ['child1'], @@ -736,7 +835,7 @@ describe('ReportPortal javascript client', () => { }); it('should return saveLog function', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { itemTempId: { children: ['child1'], @@ -752,7 +851,7 @@ describe('ReportPortal javascript client', () => { describe('sendLogWithFile', () => { it('should call getRejectAnswer if there is no itemObj with suitable itemTempId', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { id1: { children: ['child1'], @@ -769,7 +868,7 @@ describe('ReportPortal javascript client', () => { }); it('should return saveLog function', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { itemTempId: { children: ['child1'], @@ -785,7 +884,7 @@ describe('ReportPortal javascript client', () => { describe('getRequestLogWithFile', () => { it('should return restClient.create', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { id1: { children: ['child1'], @@ -800,7 +899,7 @@ describe('ReportPortal javascript client', () => { }); it('should return restClient.create with error', () => { - const client = new RPClient({ token: 'any', endpoint: 'https://rp.api', project: 'prj' }); + const client = new RPClient({ apiKey: 'any', endpoint: 'https://rp.api', project: 'prj' }); client.map = { id1: { children: ['child1'],