diff --git a/.eslintrc b/.eslintrc index d8e3c4a3..4f75a0e0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,22 +1,13 @@ { - "extends": "oclif", - "plugins": ["mocha"], - "env": { - "mocha": true + "extends": [ + "twilio", + "twilio-mocha" + ], + "parserOptions": { + "ecmaVersion": 2018 }, "rules": { - "indent": ["error", 2], - "linebreak-style": ["error", "unix"], - "quotes": ["error", "single"], - "semi": ["error", "always"], - "object-curly-spacing": ["error", "always"], - "comma-dangle": ["error", "never"], - "mocha/no-exclusive-tests": "error", - "node/no-extraneous-require": [ - "error", - { - "allowModules": ["lodash", "request", "q"] - } - ] + "global-require": "off", + "prefer-named-capture-group": "off" } -} +} \ No newline at end of file diff --git a/package.json b/package.json index fe0e1aed..ca93e4a8 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,29 @@ { "name": "@twilio/cli-core", - "description": "Core functionality for the twilio-cli", "version": "5.8.0", - "author": "Twilio @twilio", + "description": "Core functionality for the twilio-cli", + "keywords": [ + "twilio" + ], + "homepage": "https://github.com/twilio/twilio-cli-core", "bugs": "https://github.com/twilio/twilio-cli/issues", + "repository": { + "type": "git", + "url": "https://github.com/twilio/twilio-cli-core.git" + }, + "license": "MIT", + "author": "Twilio @twilio", + "main": "src/index.js", + "files": [ + "/bin", + "/src" + ], + "scripts": { + "lint": "eslint --ext js --ext jsx src/ test/", + "lint:fix": "npm run lint -- --fix", + "test": "nyc --check-coverage --lines 90 --reporter=html --reporter=text mocha --forbid-only \"test/**/*.test.js\"", + "posttest": "eslint --ignore-path .gitignore . && npm audit --audit-level=moderate" + }, "dependencies": { "@oclif/command": "^1.7.0", "@oclif/config": "^1.16.0", @@ -20,41 +40,23 @@ "tsv": "^0.2.0", "twilio": "^3.48.1" }, - "optionalDependencies": { - "keytar": "^6.0.1" - }, "devDependencies": { "@oclif/test": "^1.2.6", "@twilio/cli-test": "^2.1.0", "chai": "^4.2.0", - "eslint": "^7.3.1", - "eslint-config-oclif": "^3.1.0", - "eslint-plugin-mocha": "^7.0.1", + "eslint": "^7.5.0", + "eslint-config-twilio": "^1.28.0", + "eslint-config-twilio-mocha": "^1.28.0", "mocha": "^8.0.1", "nock": "^13.0.2", "nyc": "^15.1.0", "sinon": "^9.0.2", "tmp": "^0.2.1" }, + "optionalDependencies": { + "keytar": "^6.0.1" + }, "engines": { "node": ">=10.12.0" - }, - "files": [ - "/bin", - "/src" - ], - "homepage": "https://github.com/twilio/twilio-cli-core", - "keywords": [ - "twilio" - ], - "license": "MIT", - "main": "src/index.js", - "repository": { - "type": "git", - "url": "https://github.com/twilio/twilio-cli-core.git" - }, - "scripts": { - "posttest": "eslint --ignore-path .gitignore . && npm audit --audit-level=moderate", - "test": "nyc --check-coverage --lines 90 --reporter=html --reporter=text mocha --forbid-only \"test/**/*.test.js\"" } } diff --git a/src/base-commands/base-command.js b/src/base-commands/base-command.js index 682181e2..db98157b 100644 --- a/src/base-commands/base-command.js +++ b/src/base-commands/base-command.js @@ -1,5 +1,6 @@ -const { Command, flags } = require('@oclif/command'); +const { Command, flags: oclifFlags } = require('@oclif/command'); const { CLIError } = require('@oclif/errors'); + const pkg = require('../../package.json'); const MessageTemplates = require('../services/messaging/templates'); const { Config, ConfigData } = require('../services/config'); @@ -9,6 +10,7 @@ const { OutputFormats } = require('../services/output-formats'); const { getCommandPlugin, requireInstall } = require('../services/require-install'); const { SecureStorage } = require('../services/secure-storage'); const { instanceOf } = require('../services/javascript-utilities'); + let inquirer; // We'll lazy-load this only when it's needed. const DEFAULT_LOG_LEVEL = 'info'; @@ -40,7 +42,7 @@ class BaseCommand extends Command { this.logger = logger; this.logger.config.level = LoggingLevel[flags['cli-log-level'] || DEFAULT_LOG_LEVEL]; - this.logger.debug('Config File: ' + this.configFile.filePath); + this.logger.debug(`Config File: ${this.configFile.filePath}`); // Replace oclif's output commands this.log = this.logger.info; @@ -65,20 +67,31 @@ class BaseCommand extends Command { this.exit(error.exitCode || 1); } else { // System errors - const plugin = getCommandPlugin(this); - this.logger.error(MessageTemplates.unexpectedError({ url: this.getIssueUrl(plugin) })); + let url = ''; + try { + url = this.getIssueUrl(getCommandPlugin(this)); + } catch (e) { + // No-op + } + + this.logger.error(MessageTemplates.unexpectedError({ url })); this.logger.debug(error.message); this.logger.debug(error.stack); this.exit(1); } + + throw error; } getIssueUrl(plugin) { - const getPropertyUrl = value => value && (value.url || value); - const getPackageUrl = pjson => getPropertyUrl(pjson.bugs) || getPropertyUrl(pjson.homepage) || getPropertyUrl(pjson.repository); - - // If we found the plugin and an issue URL for it, use it. Otherwise - // fallback to our own issue URL. + const getPropertyUrl = (value) => value && (value.url || value); + const getPackageUrl = (pjson) => + getPropertyUrl(pjson.bugs) || getPropertyUrl(pjson.homepage) || getPropertyUrl(pjson.repository); + + /* + * If we found the plugin and an issue URL for it, use it. Otherwise + * fallback to our own issue URL. + */ return (plugin && getPackageUrl(plugin.pjson)) || getPackageUrl(pkg); } @@ -105,16 +118,16 @@ class BaseCommand extends Command { const limitedData = properties ? this.getLimitedData(dataArray, properties) : null; - process.stdout.write(this.outputProcessor(dataArray, limitedData || dataArray, options) + '\n'); + process.stdout.write(`${this.outputProcessor(dataArray, limitedData || dataArray, options)}\n`); } getLimitedData(dataArray, properties) { const invalidPropertyNames = new Set(); - const propNames = properties.split(',').map(p => p.trim()); - const limitedData = dataArray.map(fullItem => { + const propNames = properties.split(',').map((p) => p.trim()); + const limitedData = dataArray.map((fullItem) => { const limitedItem = {}; - propNames.forEach(p => { + propNames.forEach((p) => { let propValue = fullItem[p]; if (propValue === undefined) { @@ -137,7 +150,7 @@ class BaseCommand extends Command { if (invalidPropertyNames.size > 0) { const warn = this.logger.warn.bind(this.logger); - invalidPropertyNames.forEach(p => { + invalidPropertyNames.forEach((p) => { warn(`"${p}" is not a valid property name.`); }); } @@ -156,21 +169,21 @@ class BaseCommand extends Command { } BaseCommand.flags = { - 'cli-log-level': flags.enum({ + 'cli-log-level': oclifFlags.enum({ char: 'l', helpLabel: '-l', default: DEFAULT_LOG_LEVEL, options: Object.keys(LoggingLevel), - description: 'Level of logging messages.' + description: 'Level of logging messages.', }), - 'cli-output-format': flags.enum({ + 'cli-output-format': oclifFlags.enum({ char: 'o', helpLabel: '-o', default: DEFAULT_OUTPUT_FORMAT, options: Object.keys(OutputFormats), - description: 'Format of command output.' - }) + description: 'Format of command output.', + }), }; module.exports = BaseCommand; diff --git a/src/base-commands/twilio-client-command.js b/src/base-commands/twilio-client-command.js index e22b7602..5185a77d 100644 --- a/src/base-commands/twilio-client-command.js +++ b/src/base-commands/twilio-client-command.js @@ -1,4 +1,5 @@ const { flags } = require('@oclif/command'); + const BaseCommand = require('./base-command'); const CliRequestClient = require('../services/cli-http-client'); const { TwilioApiClient, TwilioApiFlags } = require('../services/twilio-api'); @@ -27,18 +28,16 @@ class TwilioClientCommand extends BaseCommand { const reportUnconfigured = (verb, message = '') => { const profileParam = this.flags.profile ? ` --profile "${this.flags.profile}"` : ''; - throw new TwilioCliError( - `To ${verb} the profile, run:\n\n twilio profiles:create${profileParam}${message}` - ); + throw new TwilioCliError(`To ${verb} the profile, run:\n\n twilio profiles:create${profileParam}${message}`); }; if (!this.currentProfile) { const profileName = this.flags.profile ? ` "${this.flags.profile}"` : ''; this.logger.error(`Could not find profile${profileName}.`); - reportUnconfigured('create', '\n\n' + HELP_ENVIRONMENT_VARIABLES); + reportUnconfigured('create', `\n\n${HELP_ENVIRONMENT_VARIABLES}`); } - this.logger.debug('Using profile: ' + this.currentProfile.id); + this.logger.debug(`Using profile: ${this.currentProfile.id}`); if (!this.currentProfile.apiKey || !this.currentProfile.apiSecret) { const creds = await this.secureStorage.getCredentials(this.currentProfile.id); @@ -54,11 +53,14 @@ class TwilioClientCommand extends BaseCommand { } async catch(error) { - // Append to the error message when catching API access denied errors with - // profile-auth (i.e., standard API key auth). + /* + * Append to the error message when catching API access denied errors with + * profile-auth (i.e., standard API key auth). + */ if (instanceOf(error, TwilioCliError) && error.exitCode === ACCESS_DENIED_CODE) { - if (!this.currentProfile.id.startsWith('${TWILIO')) { // Auth *not* using env vars. - error.message += '\n\n' + ACCESS_DENIED; + if (!this.currentProfile.id.startsWith('${TWILIO')) { + // Auth *not* using env vars. + error.message += `\n\n${ACCESS_DENIED}`; } } @@ -71,7 +73,7 @@ class TwilioClientCommand extends BaseCommand { } let updatedProperties = null; - Object.keys(this.constructor.PropertyFlags).forEach(propName => { + Object.keys(this.constructor.PropertyFlags).forEach((propName) => { if (this.flags[propName] !== undefined) { updatedProperties = updatedProperties || {}; const paramName = camelCase(propName); @@ -85,7 +87,7 @@ class TwilioClientCommand extends BaseCommand { async updateResource(resource, resourceSid, updatedProperties) { const results = { sid: resourceSid, - result: '?' + result: '?', }; updatedProperties = updatedProperties || this.parseProperties(); @@ -128,38 +130,36 @@ class TwilioClientCommand extends BaseCommand { accountSid: this.flags[CliFlags.ACCOUNT_SID] || this.currentProfile.accountSid, edge: process.env.TWILIO_EDGE || this.userConfig.edge, region: this.currentProfile.region, - httpClient: this.httpClient + httpClient: this.httpClient, }); } } -TwilioClientCommand.flags = Object.assign( - { - profile: flags.string({ - char: 'p', - description: 'Shorthand identifier for your profile.' - }) - }, - BaseCommand.flags -); +TwilioClientCommand.flags = { + profile: flags.string({ + char: 'p', + description: 'Shorthand identifier for your profile.', + }), + ...BaseCommand.flags, +}; TwilioClientCommand.accountSidFlag = { [CliFlags.ACCOUNT_SID]: flags.string({ - description: 'Access resources for the specified account.' - }) + description: 'Access resources for the specified account.', + }), }; TwilioClientCommand.limitFlags = { [CliFlags.LIMIT]: flags.string({ description: `The maximum number of resources to return. Use '--${CliFlags.NO_LIMIT}' to disable.`, default: 50, - exclusive: [CliFlags.NO_LIMIT] + exclusive: [CliFlags.NO_LIMIT], }), [CliFlags.NO_LIMIT]: flags.boolean({ default: false, hidden: true, - exclusive: [CliFlags.LIMIT] - }) + exclusive: [CliFlags.LIMIT], + }), }; module.exports = TwilioClientCommand; diff --git a/src/index.js b/src/index.js index e2bb3ed5..b5057670 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ module.exports = { baseCommands: { BaseCommand: require('./base-commands/base-command'), - TwilioClientCommand: require('./base-commands/twilio-client-command') + TwilioClientCommand: require('./base-commands/twilio-client-command'), }, services: { TwilioApi: require('./services/twilio-api'), @@ -13,7 +13,7 @@ module.exports = { templating: require('./services/messaging/templating'), namingConventions: require('./services/naming-conventions'), outputFormats: require('./services/output-formats'), - secureStorage: require('./services/secure-storage') + secureStorage: require('./services/secure-storage'), }, - configureEnv: require('./services/env') + configureEnv: require('./services/env'), }; diff --git a/src/services/api-schema/json-converter.js b/src/services/api-schema/json-converter.js index ff6a43fd..6c540271 100644 --- a/src/services/api-schema/json-converter.js +++ b/src/services/api-schema/json-converter.js @@ -6,7 +6,7 @@ const SCHEMA_TYPE_TO_CONVERT_FUNC_MAP = { integer: 'convertInteger', number: 'convertNumber', object: 'convertObject', - string: 'convertString' + string: 'convertString', }; /** @@ -22,7 +22,7 @@ class JsonSchemaConverter { if (schema) { if (!value) { if (!schema.nullable) { - this.logger.debug('Null value found when nullable not allowed by schema: ' + JSON.stringify(schema)); + this.logger.debug(`Null value found when nullable not allowed by schema: ${JSON.stringify(schema)}`); } return value; @@ -42,27 +42,29 @@ class JsonSchemaConverter { convertArray(schema, value) { // Recurse into the value using the schema's items schema. - return value.map(item => this.convertSchema(schema.items, item)); + return value.map((item) => this.convertSchema(schema.items, item)); } convertObject(schema, value) { const converted = {}; - let properties = schema.properties; + let { properties } = schema; - // If the schema has no properties, it is a free-form object with arbitrary - // property/value pairs. We'll map the object's keys to null-schemas so - // they'll be processed as-is (i.e., no type so just use the value). + /* + * If the schema has no properties, it is a free-form object with arbitrary + * property/value pairs. We'll map the object's keys to null-schemas so + * they'll be processed as-is (i.e., no type so just use the value). + */ if (!properties) { - const nameList = Object - .keys(value) - .map(name => ({ [name]: null })); + const nameList = Object.keys(value).map((name) => ({ [name]: null })); properties = Object.assign({}, ...nameList); } - // Convert each object property and store it in the converted object, if a - // value was provided. + /* + * Convert each object property and store it in the converted object, if a + * value was provided. + */ Object.entries(properties).forEach(([name, propSchema]) => { const { propName, propValue } = this.convertObjectProperty(propSchema, name, value[name]); diff --git a/src/services/api-schema/twilio-converter.js b/src/services/api-schema/twilio-converter.js index b3ab1713..a2c3085e 100644 --- a/src/services/api-schema/twilio-converter.js +++ b/src/services/api-schema/twilio-converter.js @@ -4,7 +4,7 @@ const { camelCase } = require('../naming-conventions'); const STRING_FORMAT_TO_CONVERT_FUNC_MAP = { 'date-time': 'convertDateTime', 'date-time-rfc-2822': 'convertDateTime', - uri: 'convertUri' + uri: 'convertUri', }; /** diff --git a/src/services/cli-http-client.js b/src/services/cli-http-client.js index a923ef1b..47ffb9b1 100644 --- a/src/services/cli-http-client.js +++ b/src/services/cli-http-client.js @@ -1,8 +1,10 @@ -var http_ = require('http'); -var https = require('https'); +const http_ = require('http'); +const https = require('https'); const os = require('os'); -const pkg = require('../../package.json'); + const qs = require('qs'); + +const pkg = require('../../package.json'); const { TwilioCliError } = require('../services/error'); const { NETWORK_ERROR } = require('../services/messaging/help-messages'); @@ -49,17 +51,14 @@ class CliRequestClient { } if (opts.username && opts.password) { - const b64Auth = Buffer.from(opts.username + ':' + opts.password).toString('base64'); - headers.Authorization = 'Basic ' + b64Auth; + const b64Auth = Buffer.from(`${opts.username}:${opts.password}`).toString('base64'); + headers.Authorization = `Basic ${b64Auth}`; } - const componentInfo = (headers['User-Agent'] || '') - .replace(' (', '|') - .replace(')', '') - .split('|'); - componentInfo.push(os.platform() + ' ' + os.release() + ' ' + os.arch()); + const componentInfo = (headers['User-Agent'] || '').replace(' (', '|').replace(')', '').split('|'); + componentInfo.push(`${os.platform()} ${os.release()} ${os.arch()}`); componentInfo.push(this.commandName); - headers['User-Agent'] = pkg.name + '/' + pkg.version + ' (' + componentInfo.join(', ') + ')'; + headers['User-Agent'] = `${pkg.name}/${pkg.version} (${componentInfo.join(', ')})`; const options = { timeout: opts.timeout || 30000, @@ -69,9 +68,9 @@ class CliRequestClient { headers, httpAgent: opts.forever ? new http_.Agent({ keepAlive: true }) : undefined, httpsAgent: opts.forever ? new https.Agent({ keepAlive: true }) : undefined, - validateStatus: status => { + validateStatus: (status) => { return status >= 100 && status < 600; - } + }, }; if (opts.data) { @@ -80,7 +79,7 @@ class CliRequestClient { if (opts.params) { options.params = opts.params; - options.paramsSerializer = params => { + options.paramsSerializer = (params) => { return qs.stringify(params, { arrayFormat: 'repeat' }); }; } @@ -91,18 +90,21 @@ class CliRequestClient { try { const response = await this.http(options); - this.logger.debug('response.statusCode: ' + response.status); - this.logger.debug('response.headers: ' + JSON.stringify(response.headers)); + this.logger.debug(`response.statusCode: ${response.status}`); + this.logger.debug(`response.headers: ${JSON.stringify(response.headers)}`); if (response.status < 200 || response.status >= 300) { const parsed = response.data; - throw new TwilioCliError(`Error code ${parsed.code} from Twilio: ${parsed.message}. See ${parsed.more_info} for more info.`, parsed.code); + throw new TwilioCliError( + `Error code ${parsed.code} from Twilio: ${parsed.message}. See ${parsed.more_info} for more info.`, + parsed.code, + ); } return { body: response.data, statusCode: response.status, - headers: response.headers + headers: response.headers, }; } catch (error) { if (NETWORK_ERROR_CODES.has(error.code)) { @@ -115,7 +117,7 @@ class CliRequestClient { logRequest(options) { this.logger.debug('-- BEGIN Twilio API Request --'); - this.logger.debug(options.method + ' ' + options.url); + this.logger.debug(`${options.method} ${options.url}`); if (options.data) { this.logger.debug('Form data:'); @@ -127,15 +129,15 @@ class CliRequestClient { this.logger.debug(options.params); } - const customHeaders = Object.keys(options.headers).filter(header => { + const customHeaders = Object.keys(options.headers).filter((header) => { return !STANDARD_HEADERS.includes(header.toLowerCase()); }); if (customHeaders) { this.logger.debug('Custom HTTP Headers:'); - customHeaders.forEach(header => this.logger.debug(header + ': ' + options.headers[header])); + customHeaders.forEach((header) => this.logger.debug(`${header}: ${options.headers[header]}`)); } - this.logger.debug('User-Agent: ' + options.headers['User-Agent']); + this.logger.debug(`User-Agent: ${options.headers['User-Agent']}`); this.logger.debug('-- END Twilio API Request --'); } } diff --git a/src/services/config.js b/src/services/config.js index 3a13d13b..3c1a1071 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -1,5 +1,8 @@ -const fs = require('fs-extra'); +/* eslint-disable max-classes-per-file */ const path = require('path'); + +const fs = require('fs-extra'); + const MessageTemplates = require('./messaging/templates'); const CLI_NAME = 'twilio-cli'; @@ -22,34 +25,34 @@ class ConfigData { } getProfileFromEnvironment() { - const { - TWILIO_ACCOUNT_SID, - TWILIO_AUTH_TOKEN, - TWILIO_API_KEY, - TWILIO_API_SECRET, - TWILIO_REGION - } = process.env; - if (!TWILIO_ACCOUNT_SID) return; - - if (TWILIO_API_KEY && TWILIO_API_SECRET) + const { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_API_KEY, TWILIO_API_SECRET, TWILIO_REGION } = process.env; + if (!TWILIO_ACCOUNT_SID) { + return undefined; + } + + if (TWILIO_API_KEY && TWILIO_API_SECRET) { return { // eslint-disable-next-line no-template-curly-in-string id: '${TWILIO_API_KEY}/${TWILIO_API_SECRET}', accountSid: TWILIO_ACCOUNT_SID, apiKey: TWILIO_API_KEY, apiSecret: TWILIO_API_SECRET, - region: TWILIO_REGION + region: TWILIO_REGION, }; + } - if (TWILIO_AUTH_TOKEN) + if (TWILIO_AUTH_TOKEN) { return { // eslint-disable-next-line no-template-curly-in-string id: '${TWILIO_ACCOUNT_SID}/${TWILIO_AUTH_TOKEN}', accountSid: TWILIO_ACCOUNT_SID, apiKey: TWILIO_ACCOUNT_SID, apiSecret: TWILIO_AUTH_TOKEN, - region: TWILIO_REGION + region: TWILIO_REGION, }; + } + + return undefined; } getProfileById(profileId) { @@ -63,7 +66,7 @@ class ConfigData { if (profileId) { // Clean the profile ID. profileId = this.sanitize(profileId); - profile = this.profiles.find(profile => profile.id === profileId); + profile = this.profiles.find((p) => p.id === profileId); } else { profile = this.getActiveProfile(); } @@ -81,13 +84,15 @@ class ConfigData { return profile; } } + + return undefined; } getActiveProfile() { let profile; if (this.profiles.length > 0) { if (this.activeProfile) { - profile = this.profiles.find(profile => profile.id === this.activeProfile); + profile = this.profiles.find((p) => p.id === this.activeProfile); } if (!profile) { profile = this.profiles[0]; @@ -97,7 +102,7 @@ class ConfigData { } removeProfile(profileToRemove) { - this.profiles = this.profiles.filter(profile => { + this.profiles = this.profiles.filter((profile) => { return profile.id !== profileToRemove.id; }); if (profileToRemove.id === this.activeProfile) { @@ -143,7 +148,7 @@ class ConfigData { this.prompts = configObj.prompts || {}; // Note the historical 'projects' naming. configObj.profiles = configObj.projects || []; - configObj.profiles.forEach(profile => this.addProfile(profile.id, profile.accountSid, profile.region)); + configObj.profiles.forEach((profile) => this.addProfile(profile.id, profile.accountSid, profile.region)); this.setActiveProfile(configObj.activeProject); } @@ -177,7 +182,7 @@ class Config { prompts: configData.prompts, // Note the historical 'projects' naming. projects: configData.profiles, - activeProject: configData.activeProfile + activeProject: configData.activeProfile, }; fs.mkdirSync(this.configDir, { recursive: true }); @@ -190,5 +195,5 @@ class Config { module.exports = { CLI_NAME, Config, - ConfigData + ConfigData, }; diff --git a/src/services/env.js b/src/services/env.js index 7e32027f..0712429c 100644 --- a/src/services/env.js +++ b/src/services/env.js @@ -1,18 +1,15 @@ const os = require('os'); const path = require('path'); + const { CLI_NAME } = require('./config'); const configureEnv = () => { const home = process.env.HOME || process.env.USERPROFILE || os.homedir(); const twilioDir = path.join(home, `.${CLI_NAME}`); - const envDirs = [ - 'TWILIO_CACHE_DIR', - 'TWILIO_CONFIG_DIR', - 'TWILIO_DATA_DIR' - ]; + const envDirs = ['TWILIO_CACHE_DIR', 'TWILIO_CONFIG_DIR', 'TWILIO_DATA_DIR']; - envDirs.forEach(envVarName => { + envDirs.forEach((envVarName) => { if (!process.env[envVarName]) { process.env[envVarName] = twilioDir; } diff --git a/src/services/javascript-utilities.js b/src/services/javascript-utilities.js index c6ff9d11..de2a699a 100644 --- a/src/services/javascript-utilities.js +++ b/src/services/javascript-utilities.js @@ -19,7 +19,7 @@ const translateObject = (obj, keyFunc, valueFunc) => { } if (Array.isArray(obj)) { - return obj.map(item => translateObject(item, keyFunc, valueFunc)); + return obj.map((item) => translateObject(item, keyFunc, valueFunc)); } if (typeof obj === 'object') { @@ -63,15 +63,15 @@ const translateValues = (obj, valueFunc) => { return translateObject(obj, null, valueFunc); }; -const sleep = ms => { - return new Promise(resolve => setTimeout(resolve, ms)); +const sleep = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)); }; const splitArray = (array, testFunc) => { const left = []; const right = []; - array.forEach(item => testFunc(item) ? left.push(item) : right.push(item)); + array.forEach((item) => (testFunc(item) ? left.push(item) : right.push(item))); return [left, right]; }; @@ -105,5 +105,5 @@ module.exports = { translateValues, sleep, splitArray, - instanceOf + instanceOf, }; diff --git a/src/services/messaging/logging.js b/src/services/messaging/logging.js index 41f2bc29..e77bd925 100644 --- a/src/services/messaging/logging.js +++ b/src/services/messaging/logging.js @@ -5,14 +5,14 @@ const LoggingLevel = { info: 0, warn: 1, error: 2, - none: 10 + none: 10, }; const LoggingLevelStyle = { - [LoggingLevel.debug]: msg => chalk.dim('[DEBUG] ' + msg), - [LoggingLevel.info]: msg => msg, - [LoggingLevel.warn]: msg => chalk.italic(' » ' + msg), - [LoggingLevel.error]: msg => chalk.bold(' » ' + msg) + [LoggingLevel.debug]: (msg) => chalk.dim(`[DEBUG] ${msg}`), + [LoggingLevel.info]: (msg) => msg, + [LoggingLevel.warn]: (msg) => chalk.italic(` » ${msg}`), + [LoggingLevel.error]: (msg) => chalk.bold(` » ${msg}`), }; class Logger { @@ -41,17 +41,17 @@ class Logger { if (level >= this.config.level) { const message = typeof msg === 'string' ? msg : JSON.stringify(msg); - process.stderr.write(LoggingLevelStyle[level](message) + '\n'); + process.stderr.write(`${LoggingLevelStyle[level](message)}\n`); } } } const logger = new Logger({ - level: LoggingLevel.info + level: LoggingLevel.info, }); module.exports = { LoggingLevel, Logger, // class - logger // global instance + logger, // global instance }; diff --git a/src/services/messaging/templating.js b/src/services/messaging/templating.js index b6f77f4c..8bb3af92 100644 --- a/src/services/messaging/templating.js +++ b/src/services/messaging/templating.js @@ -5,8 +5,10 @@ const templatize = (templateStrings, ...templateKeys) => { const result = [templateStrings[0]]; templateKeys.forEach((key, i) => { - // Numerical keys will perform a 0-based index lookup on the provided values. - // Others will perform a string-key lookup on the last value. + /* + * Numerical keys will perform a 0-based index lookup on the provided values. + * Others will perform a string-key lookup on the last value. + */ const value = Number.isInteger(key) ? values[key] : dict[key]; // Append the lookup value and the next string in the template. @@ -20,5 +22,5 @@ const templatize = (templateStrings, ...templateKeys) => { }; module.exports = { - templatize + templatize, }; diff --git a/src/services/naming-conventions.js b/src/services/naming-conventions.js index b5ba5b6e..c01e3a29 100644 --- a/src/services/naming-conventions.js +++ b/src/services/naming-conventions.js @@ -1,4 +1,4 @@ -const kebabCase = input => { +const kebabCase = (input) => { return input .trim() .replace(/[ _]/g, '-') // from snake_case (or spaces) @@ -8,18 +8,18 @@ const kebabCase = input => { .replace(/^-|-$/g, ''); // remove leading and trailing dashes }; -const camelCase = input => { +const camelCase = (input) => { return input .trim() - .replace(/^[-_]+|[-_]+$/g, '')// remove leading and trailing dashes and underscores - .replace(/^[A-Z]/, g => g[0].toLowerCase()) // from PascalCase - .replace(/\.[A-Z]/g, g => g.toLowerCase()) // from dot-separated - .replace(/[A-Z]{2,}/g, g => g.toLowerCase()) // consecutive caps (e.g. "AWS") TODO: What about AWSRoute53? - .replace(/[-_ ]([a-z])/g, g => g[1].toUpperCase()) // from kebab-case or snake_case (or spaces) + .replace(/^[-_]+|[-_]+$/g, '') // remove leading and trailing dashes and underscores + .replace(/^[A-Z]/, (g) => g[0].toLowerCase()) // from PascalCase + .replace(/\.[A-Z]/g, (g) => g.toLowerCase()) // from dot-separated + .replace(/[A-Z]{2,}/g, (g) => g.toLowerCase()) // consecutive caps (e.g. "AWS") TODO: What about AWSRoute53? + .replace(/[-_ ]([a-z])/g, (g) => g[1].toUpperCase()) // from kebab-case or snake_case (or spaces) .replace(/ /g, ''); // remove any remaining spaces }; -const snakeCase = input => { +const snakeCase = (input) => { return input .trim() .replace(/[ -]/g, '_') // from kebab-case (or spaces) @@ -29,13 +29,13 @@ const snakeCase = input => { .replace(/^_|_$/g, ''); // remove leading and trailing underscores }; -const capitalize = input => { - return input.trim().replace(/^[a-z]/, g => g[0].toUpperCase()); // upper the first character +const capitalize = (input) => { + return input.trim().replace(/^[a-z]/, (g) => g[0].toUpperCase()); // upper the first character }; -const pascalCase = input => { +const pascalCase = (input) => { return camelCase(input) // camelize first - .replace(/(^|\.)[a-z]/g, g => g.toUpperCase()); // upper the first character and after each dot + .replace(/(^|\.)[a-z]/g, (g) => g.toUpperCase()); // upper the first character and after each dot }; module.exports = { @@ -43,5 +43,5 @@ module.exports = { camelCase, snakeCase, capitalize, - pascalCase + pascalCase, }; diff --git a/src/services/open-api-client.js b/src/services/open-api-client.js index 3f7023f3..07e0d691 100644 --- a/src/services/open-api-client.js +++ b/src/services/open-api-client.js @@ -1,4 +1,5 @@ const url = require('url'); + const { logger } = require('./messaging/logging'); const { doesObjectHaveProperty } = require('./javascript-utilities'); const JsonSchemaConverter = require('./api-schema/json-converter'); @@ -11,7 +12,7 @@ class OpenApiClient { } async request(opts) { - opts = Object.assign({}, opts); + opts = { ...opts }; const domain = this.apiBrowser.domains[opts.domain]; @@ -31,7 +32,7 @@ class OpenApiClient { throw new Error(`Operation not found: ${opts.domain}.${opts.path}.${opts.method}`); } - const isPost = (opts.method.toLowerCase() === 'post'); + const isPost = opts.method.toLowerCase() === 'post'; const params = this.getParams(opts, operation); if (!opts.uri) { @@ -50,8 +51,8 @@ class OpenApiClient { uri.hostname = this.getHost(uri.hostname, opts); opts.uri = uri.href; - opts.params = (isPost ? null : params); - opts.data = (isPost ? params : null); + opts.params = isPost ? null : params; + opts.data = isPost ? params : null; const response = await this.httpClient.request(opts); @@ -60,9 +61,11 @@ class OpenApiClient { getParams(opts, operation) { const params = {}; - operation.parameters.forEach(parameter => { - // Build the actual request params from the spec's query parameters. This - // effectively drops all params that are not in the spec. + operation.parameters.forEach((parameter) => { + /* + * Build the actual request params from the spec's query parameters. This + * effectively drops all params that are not in the spec. + */ if (parameter.in === 'query' && doesObjectHaveProperty(opts.data, parameter.name)) { let value = opts.data[parameter.name]; if (parameter.schema.type === 'boolean') { @@ -76,8 +79,10 @@ class OpenApiClient { } getUri(opts) { - // Evaluate the request path by replacing path parameters with their value - // from the request data. + /* + * Evaluate the request path by replacing path parameters with their value + * from the request data. + */ return opts.path.replace(/{(.+?)}/g, (fullMatch, pathNode) => { let value = ''; @@ -95,7 +100,9 @@ class OpenApiClient { getHost(host, opts) { if (opts.region || opts.edge) { const domain = host.split('.').slice(-2).join('.'); - const prefix = host.split('.' + domain)[0]; + const prefix = host.split(`.${domain}`)[0]; + + // eslint-disable-next-line prefer-const let [product, edge, region] = prefix.split('.'); if (edge && !region) { region = edge; @@ -103,7 +110,7 @@ class OpenApiClient { } edge = opts.edge || edge; region = opts.region || region || (opts.edge && 'us1'); - return [product, edge, region, domain].filter(part => part).join('.'); + return [product, edge, region, domain].filter((part) => part).join('.'); } return host; } @@ -125,16 +132,16 @@ class OpenApiClient { let response = operation.responses[statusCode]; if (!response) { - const statusCodeRange = statusCode.toString()[0] + 'XX'; + const statusCodeRange = `${statusCode.toString()[0]}XX`; response = operation.responses[statusCodeRange]; if (!response) { logger.debug(`Response schema not found for status code ${statusCode} (${statusCodeRange})`); - return; + return undefined; } } - const schema = response.content[contentType].schema; + const { schema } = response.content[contentType]; return this.evaluateRefs(schema, domain); } @@ -168,11 +175,14 @@ class OpenApiClient { } let node = domain; - local.split('/').filter(n => n).forEach(nodeName => { - if (doesObjectHaveProperty(node, nodeName)) { - node = node[nodeName]; - } - }); + local + .split('/') + .filter((n) => n) + .forEach((nodeName) => { + if (doesObjectHaveProperty(node, nodeName)) { + node = node[nodeName]; + } + }); if (!node) { logger.debug(`Ref not found: ${ref}`); diff --git a/src/services/output-formats/columns.js b/src/services/output-formats/columns.js index cb10bc1d..e6fb6c1d 100644 --- a/src/services/output-formats/columns.js +++ b/src/services/output-formats/columns.js @@ -1,5 +1,6 @@ const chalk = require('chalk'); const columnify = require('columnify'); + const { capitalize } = require('../naming-conventions'); function headingTransform(heading) { @@ -9,7 +10,7 @@ function headingTransform(heading) { heading = capitalize(heading); heading = heading .split(' ') - .map(word => (capitalizeWords.indexOf(word) > -1 ? word.toUpperCase() : word)) + .map((word) => (capitalizeWords.indexOf(word) > -1 ? word.toUpperCase() : word)) .join(' '); return chalk.bold(heading); } @@ -20,21 +21,16 @@ module.exports = (fullData, limitedData, options) => { } const columns = Object.keys(limitedData[0]) - .map(key => ({ key, value: { headingTransform } })) + .map((key) => ({ key, value: { headingTransform } })) .reduce((map, obj) => { map[obj.key] = obj.value; return map; }, {}); options = options || {}; - return columnify( - limitedData, - Object.assign( - { - columnSplitter: ' ', - config: columns - }, - options - ) - ); + return columnify(limitedData, { + columnSplitter: ' ', + config: columns, + ...options, + }); }; diff --git a/src/services/output-formats/index.js b/src/services/output-formats/index.js index 11c50dc3..9379d53d 100644 --- a/src/services/output-formats/index.js +++ b/src/services/output-formats/index.js @@ -1,9 +1,9 @@ const OutputFormats = { columns: require('./columns'), json: require('./json'), - tsv: require('./tsv') + tsv: require('./tsv'), }; module.exports = { - OutputFormats + OutputFormats, }; diff --git a/src/services/output-formats/json.js b/src/services/output-formats/json.js index 10292eb6..20078bfe 100644 --- a/src/services/output-formats/json.js +++ b/src/services/output-formats/json.js @@ -1 +1 @@ -module.exports = data => JSON.stringify(data, null, 2); +module.exports = (data) => JSON.stringify(data, null, 2); diff --git a/src/services/require-install.js b/src/services/require-install.js index 4c3e8776..aae9ac04 100644 --- a/src/services/require-install.js +++ b/src/services/require-install.js @@ -1,28 +1,33 @@ const path = require('path'); + const semver = require('semver'); const Plugins = require('@oclif/plugin-plugins').default; + +const { TwilioCliError } = require('../services/error'); const corePJSON = require('../../package.json'); const { logger } = require('./messaging/logging'); /** * Retrieves the plugin for a given command. */ -const getCommandPlugin = command => { - for (let plugin of command.config.plugins || []) { - for (let pluginCommand of plugin.commands) { +const getCommandPlugin = (command) => { + for (const plugin of command.config.plugins || []) { + for (const pluginCommand of plugin.commands) { if (pluginCommand.id === command.id || pluginCommand.aliases.includes(command.id)) { - // Check the plugin options/config name first. This will contain the - // name of the top-level plugin in the case of "dynamic" plugins. All - // such plugins should really use the same dependency location. - if (plugin.options.name) { - plugin = command.config.plugins.find(p => p.name === plugin.options.name); - } - - logger.debug(`Found command "${command.id}" plugin: ${plugin.name}`); - return command.config.plugins.find(p => p.name === plugin.name); + /* + * Check the plugin options/config name first. This will contain the + * name of the top-level plugin in the case of "dynamic" plugins. All + * such plugins should really use the same dependency location. + */ + const match = plugin.options.name ? command.config.plugins.find((p) => p.name === plugin.options.name) : plugin; + + logger.debug(`Found command "${command.id}" plugin: ${match.name}`); + return command.config.plugins.find((p) => p.name === match.name); } } } + + throw new TwilioCliError('No plugin was found'); }; /** @@ -40,6 +45,8 @@ const getPackageVersion = (packagePath, errors = null) => { } else { errors.push(error); } + + return undefined; } }; @@ -47,7 +54,8 @@ const getPackageVersion = (packagePath, errors = null) => { * Retrieves the dependency version given a dependency name and package JSON. */ const getDependencyVersion = (packageName, pluginPJSON) => { - for (const pjson of [pluginPJSON, corePJSON]) { // Check the plugin first. + for (const pjson of [pluginPJSON, corePJSON]) { + // Check the plugin first. for (const location of ['dependencies', 'optionalDependencies']) { const version = pjson && pjson[location] && pjson[location][packageName]; @@ -57,6 +65,8 @@ const getDependencyVersion = (packageName, pluginPJSON) => { } } } + + return undefined; }; /** @@ -104,8 +114,10 @@ const requireInstall = async (packageName, command) => { const plugins = new Plugins({ dataDir: pluginPath, cacheDir: pluginPath }); try { - // Init the PJSON in case it doesn't exist. This is required by yarn or it - // moves up the dir tree until it finds one. + /* + * Init the PJSON in case it doesn't exist. This is required by yarn or it + * moves up the dir tree until it finds one. + */ await plugins.createPJSON(); // Force install the package in case it's a native module that needs rebuilding. @@ -122,7 +134,7 @@ const requireInstall = async (packageName, command) => { // Debug log any lazy errors we swallowed earlier. if (errors) { logger.debug(`Error loading/installing ${packageName}:`); - errors.forEach(lazyError => logger.debug(lazyError)); + errors.forEach((lazyError) => logger.debug(lazyError)); } throw error; @@ -134,5 +146,5 @@ module.exports = { getPackageVersion, getDependencyVersion, checkVersion, - requireInstall + requireInstall, }; diff --git a/src/services/require-native.js b/src/services/require-native.js deleted file mode 100644 index 5b6a883c..00000000 --- a/src/services/require-native.js +++ /dev/null @@ -1,35 +0,0 @@ -const { logger } = require('./messaging/logging'); - -/** - * Loads a module by first checking in the command execution path and falling - * back to the standard modules path. - */ -const requireNative = id => { - // Since native modules are compiled for the specific version of Node they - // were installed with, we don't want plugins that depend on us to depend on - // them. Otherwise, they'd need to reinstall each time Node changes since it - // requires the native module to be reinstalled. For this reason, we don't - // expect to find native modules under the normal dependency tree. They - // should exist with the CLI's dependencies. We'll attempt to find out - // where that is based on the location of the script that kicked off the - // process. Resolve any symlinks along the way to find the "real" path - // and then load up the module. - try { - const realAppPath = require('fs').realpathSync(process.argv[1]); - const realModulePath = require.resolve(id, { paths: [realAppPath] }); - - return require(realModulePath); - } catch (error) { - logger.debug(`Failed to find the ${id} module with the CLI: ` + error.message); - } - - // If the above fails for whatever reason, fallback to the standard location - // since some lib is better than no lib. - try { - return require(id); - } catch (error) { - logger.debug(`Failed to find the ${id} module anywhere: ` + error.message); - } -}; - -module.exports = { requireNative }; diff --git a/src/services/secure-storage.js b/src/services/secure-storage.js index f0e189c7..38bd7146 100644 --- a/src/services/secure-storage.js +++ b/src/services/secure-storage.js @@ -6,13 +6,13 @@ const { logger } = require('./messaging/logging'); const STORAGE_LOCATIONS = { KEYCHAIN: 'keychain', WIN_CRED_VAULT: 'win_cred_vault', - LIBSECRET: 'libsecret' + LIBSECRET: 'libsecret', }; const PLATFORM_TO_LOCATION = { darwin: STORAGE_LOCATIONS.KEYCHAIN, win32: STORAGE_LOCATIONS.WIN_CRED_VAULT, - linux: STORAGE_LOCATIONS.LIBSECRET + linux: STORAGE_LOCATIONS.LIBSECRET, }; class SecureStorage { @@ -32,7 +32,7 @@ class SecureStorage { } catch (error) { logger.debug(`Error loading keytar: ${error}`); // If we can't load up keytar, tell the user that maybe they should just stick to env vars. - throw new TwilioCliError('Secure credential storage failed to load.\n\n' + HELP_ENVIRONMENT_VARIABLES); + throw new TwilioCliError(`Secure credential storage failed to load.\n\n${HELP_ENVIRONMENT_VARIABLES}`); } } @@ -41,7 +41,7 @@ class SecureStorage { async saveCredentials(profileId, username, password) { await this.loadKeytar(); - await this.keytar.setPassword(CLI_NAME, profileId, username + '|' + password); + await this.keytar.setPassword(CLI_NAME, profileId, `${username}|${password}`); } async removeCredentials(profileId) { @@ -66,7 +66,7 @@ class SecureStorage { return { apiKey, - apiSecret + apiSecret, }; } @@ -77,5 +77,5 @@ class SecureStorage { module.exports = { SecureStorage, - STORAGE_LOCATIONS + STORAGE_LOCATIONS, }; diff --git a/src/services/twilio-api/api-browser.js b/src/services/twilio-api/api-browser.js index 83f3f8da..e1142bcd 100644 --- a/src/services/twilio-api/api-browser.js +++ b/src/services/twilio-api/api-browser.js @@ -1,13 +1,16 @@ const fs = require('fs'); + const { camelCase } = require('../naming-conventions'); + let apiSpec; // Lazy-loaded below. const OPERATIONS = ['post', 'get', 'delete']; class TwilioApiBrowser { - constructor(apiSpec) { - apiSpec = apiSpec || this.loadApiSpecFromDisk(); - this.domains = this.loadDomains(apiSpec); + constructor(spec) { + spec = spec || this.loadApiSpecFromDisk(); + + this.domains = this.loadDomains(spec); } loadApiSpecFromDisk() { @@ -15,9 +18,10 @@ class TwilioApiBrowser { const specPattern = /twilio_(.+)\.json/; const specNameIndex = 1; - apiSpec = fs.readdirSync(__dirname) - .filter(filename => filename.match(specPattern)) - .map(filename => { + apiSpec = fs + .readdirSync(__dirname) + .filter((filename) => filename.match(specPattern)) + .map((filename) => { const domainName = filename.match(specPattern)[specNameIndex]; return { [domainName]: require(`./${filename}`) }; @@ -29,12 +33,12 @@ class TwilioApiBrowser { return apiSpec; } - loadDomains(apiSpec) { + loadDomains(obj) { // Clone the spec since we'll be modifying it. - const domains = JSON.parse(JSON.stringify(apiSpec)); + const domains = JSON.parse(JSON.stringify(obj)); - Object.values(domains).forEach(spec => { - Object.values(spec.paths).forEach(path => { + Object.values(domains).forEach((spec) => { + Object.values(spec.paths).forEach((path) => { // Naive assumption: The Twilio APIs only have a single server. path.server = path.servers[0].url; delete path.servers; @@ -43,14 +47,16 @@ class TwilioApiBrowser { path.description = path.description.replace(/(\r\n|\n|\r)/gm, ' '); // Move the operations into an operations object. - OPERATIONS.forEach(operationName => { + OPERATIONS.forEach((operationName) => { if (operationName in path) { const operation = path[operationName]; path.operations[operationName] = operation; delete path[operationName]; - // Convert all the request body properties to query parameters for - // simpler parsing downstream. + /* + * Convert all the request body properties to query parameters for + * simpler parsing downstream. + */ const parameters = this.requestPropertiesToParameters(operation.requestBody); if (parameters.length > 0) { @@ -79,7 +85,7 @@ class TwilioApiBrowser { const parameters = []; const content = (requestBody || {}).content || {}; - Object.values(content).forEach(type => { + Object.values(content).forEach((type) => { const typeSchema = type.schema || {}; const properties = typeSchema.properties || {}; const required = typeSchema.required || []; @@ -90,7 +96,7 @@ class TwilioApiBrowser { schema, in: 'query', required: required.includes(name), - description: schema.description + description: schema.description, }); }); }); diff --git a/src/services/twilio-api/index.js b/src/services/twilio-api/index.js index c39d6923..fed7ee4d 100644 --- a/src/services/twilio-api/index.js +++ b/src/services/twilio-api/index.js @@ -4,5 +4,5 @@ const { TwilioApiClient, TwilioApiFlags } = require('./twilio-client'); module.exports = { TwilioApiBrowser, TwilioApiClient, - TwilioApiFlags + TwilioApiFlags, }; diff --git a/src/services/twilio-api/twilio-client.js b/src/services/twilio-api/twilio-client.js index 3ebe90ed..0be4898a 100644 --- a/src/services/twilio-api/twilio-client.js +++ b/src/services/twilio-api/twilio-client.js @@ -10,7 +10,7 @@ const TwilioApiFlags = { ACCOUNT_SID: 'AccountSid', PAGE_SIZE: 'PageSize', LIMIT: 'Limit', - NO_LIMIT: 'NoLimit' + NO_LIMIT: 'NoLimit', }; class TwilioApiClient { @@ -26,7 +26,7 @@ class TwilioApiClient { this.apiClient = new OpenApiClient({ httpClient: opts.httpClient, apiBrowser: new TwilioApiBrowser(), - converter: new TwilioSchemaConverter() + converter: new TwilioSchemaConverter(), }); if (!this.username) { @@ -105,7 +105,7 @@ class TwilioApiClient { domain: opts.domain, host: opts.host, path: opts.path, - uri: nextPageUri + uri: nextPageUri, }; } @@ -115,7 +115,7 @@ class TwilioApiClient { getLimit(options) { // 'no-limit' outranks 'limit' so begone. if (!options || options[TwilioApiFlags.NO_LIMIT]) { - return; + return undefined; } const limit = options[TwilioApiFlags.LIMIT]; @@ -162,7 +162,7 @@ class TwilioApiClient { * @param {boolean} [opts.allowRedirects] - Should the client follow redirects */ async request(opts) { - opts = Object.assign({}, opts); + opts = { ...opts }; opts.username = opts.username || this.username; opts.password = opts.password || this.password; @@ -182,7 +182,10 @@ class TwilioApiClient { } if (!opts.uri) { - if (opts.path.includes(TwilioApiFlags.ACCOUNT_SID) && !doesObjectHaveProperty(opts.pathParams, TwilioApiFlags.ACCOUNT_SID)) { + if ( + opts.path.includes(TwilioApiFlags.ACCOUNT_SID) && + !doesObjectHaveProperty(opts.pathParams, TwilioApiFlags.ACCOUNT_SID) + ) { opts.pathParams[TwilioApiFlags.ACCOUNT_SID] = this.accountSid; } } @@ -195,5 +198,5 @@ class TwilioApiClient { module.exports = { TwilioApiClient, - TwilioApiFlags + TwilioApiFlags, }; diff --git a/test/base-commands/base-command.test.js b/test/base-commands/base-command.test.js index cc8d4745..abd0c860 100644 --- a/test/base-commands/base-command.test.js +++ b/test/base-commands/base-command.test.js @@ -1,19 +1,20 @@ const path = require('path'); + const { expect, test } = require('@twilio/cli-test'); + const BaseCommand = require('../../src/base-commands/base-command'); const { Logger, LoggingLevel } = require('../../src/services/messaging/logging'); const { OutputFormats } = require('../../src/services/output-formats'); const { Config } = require('../../src/services/config'); const { TwilioCliError } = require('../../src/services/error'); -const baseCommandTest = test.twilioCliEnv().do(async ctx => { +const baseCommandTest = test.twilioCliEnv().do(async (ctx) => { ctx.testCmd = new BaseCommand([], ctx.fakeConfig); await ctx.testCmd.run(); }); -const childCommandTest = test.twilioCliEnv().do(async ctx => { - class ChildCommand extends BaseCommand { - } +const childCommandTest = test.twilioCliEnv().do(async (ctx) => { + class ChildCommand extends BaseCommand {} ctx.testCmd = new ChildCommand([], ctx.fakeConfig); await ctx.testCmd.run(); @@ -21,7 +22,7 @@ const childCommandTest = test.twilioCliEnv().do(async ctx => { describe('base-commands', () => { describe('base-command', () => { - baseCommandTest.stderr().it('should initialize properly', ctx => { + baseCommandTest.stderr().it('should initialize properly', (ctx) => { expect(ctx.testCmd.outputProcessor).to.equal(OutputFormats.columns); expect(ctx.testCmd.logger).to.be.an.instanceOf(Logger); expect(ctx.testCmd.logger.config.level).to.equal(LoggingLevel.info); @@ -29,7 +30,7 @@ describe('base-commands', () => { expect(ctx.stderr).to.equal(''); }); - childCommandTest.stderr().it('should initialize properly from children', ctx => { + childCommandTest.stderr().it('should initialize properly from children', (ctx) => { expect(ctx.testCmd.outputProcessor).to.equal(OutputFormats.columns); expect(ctx.testCmd.logger).to.be.an.instanceOf(Logger); expect(ctx.testCmd.logger.config.level).to.equal(LoggingLevel.info); @@ -40,11 +41,11 @@ describe('base-commands', () => { test .twilioCliEnv(Config) .stderr() - .do(async ctx => { + .do(async (ctx) => { ctx.testCmd = new BaseCommand(['-l', 'debug'], ctx.fakeConfig); await ctx.testCmd.run(); }) - .it('should debug log the config file path', ctx => { + .it('should debug log the config file path', (ctx) => { expect(ctx.testCmd.logger.config.level).to.equal(LoggingLevel.debug); const expectedConfigFile = path.join(ctx.fakeConfig.configDir, 'config.json'); expect(ctx.stderr).to.contain(`[DEBUG] Config File: ${expectedConfigFile}`); @@ -52,27 +53,27 @@ describe('base-commands', () => { baseCommandTest .stderr() - .do(ctx => ctx.testCmd.catch(new TwilioCliError('oy!'))) + .do((ctx) => ctx.testCmd.catch(new TwilioCliError('oy!'))) .exit(1) - .it('can catch errors and exit', ctx => { + .it('can catch errors and exit', (ctx) => { expect(ctx.stderr).to.contain('oy!'); }); test .twilioCliEnv() - .do(ctx => { + .do((ctx) => { ctx.testCmd = new BaseCommand([], ctx.fakeConfig); }) - .it('can catch errors before initialization', async ctx => { + .it('can catch errors before initialization', async (ctx) => { await expect(ctx.testCmd.catch(new TwilioCliError('hey-o!'))).to.be.rejectedWith(TwilioCliError); }); describe('getIssueUrl', () => { - baseCommandTest.it('follows the proper precedence order', ctx => { + baseCommandTest.it('follows the proper precedence order', (ctx) => { const pjson = { bugs: 'could be', homepage: 'maybe', - repository: 'nope' + repository: 'nope', }; expect(ctx.testCmd.getIssueUrl({ pjson })).to.equal('could be'); @@ -84,26 +85,34 @@ describe('base-commands', () => { expect(ctx.testCmd.getIssueUrl({ pjson })).to.equal('nope'); }); - baseCommandTest.it('handles url properties', ctx => { + baseCommandTest.it('handles url properties', (ctx) => { expect(ctx.testCmd.getIssueUrl({ pjson: { bugs: { email: 'me', url: 'you' } } })).to.equal('you'); }); - baseCommandTest.it('use the main repo when no url is found', ctx => { - expect(ctx.testCmd.getIssueUrl({ pjson: { anything: 'nothing' } })).to.equal('https://github.com/twilio/twilio-cli/issues'); + baseCommandTest.it('use the main repo when no url is found', (ctx) => { + expect(ctx.testCmd.getIssueUrl({ pjson: { anything: 'nothing' } })).to.equal( + 'https://github.com/twilio/twilio-cli/issues', + ); }); }); describe('sanitizeDateString', () => { - baseCommandTest.it('check date is sliced correctly', ctx => { - expect(ctx.testCmd.sanitizeDateString('Fri May 24 2019 11:43:11 GMT-0600 (MDT)')).to.equal('May 24 2019 11:43:11 GMT-0600'); - }); - baseCommandTest.it('check other timezone date is sliced correctly', ctx => { - expect(ctx.testCmd.sanitizeDateString('Fri May 24 2019 11:43:11 GMT-0700 (PDT)')).to.equal('May 24 2019 11:43:11 GMT-0700'); - }); - baseCommandTest.it('check output if timezone in parenthesis is not included', ctx => { - expect(ctx.testCmd.sanitizeDateString('Fri May 24 2019 11:43:11 GMT-0700')).to.equal('May 24 2019 11:43:11 GMT-0700'); - }); - baseCommandTest.it('return empty string if the date is empty', ctx => { + baseCommandTest.it('check date is sliced correctly', (ctx) => { + expect(ctx.testCmd.sanitizeDateString('Fri May 24 2019 11:43:11 GMT-0600 (MDT)')).to.equal( + 'May 24 2019 11:43:11 GMT-0600', + ); + }); + baseCommandTest.it('check other timezone date is sliced correctly', (ctx) => { + expect(ctx.testCmd.sanitizeDateString('Fri May 24 2019 11:43:11 GMT-0700 (PDT)')).to.equal( + 'May 24 2019 11:43:11 GMT-0700', + ); + }); + baseCommandTest.it('check output if timezone in parenthesis is not included', (ctx) => { + expect(ctx.testCmd.sanitizeDateString('Fri May 24 2019 11:43:11 GMT-0700')).to.equal( + 'May 24 2019 11:43:11 GMT-0700', + ); + }); + baseCommandTest.it('return empty string if the date is empty', (ctx) => { expect(ctx.testCmd.sanitizeDateString('')).to.equal(''); }); }); @@ -111,37 +120,55 @@ describe('base-commands', () => { describe('output', () => { const outputTest = baseCommandTest.stdout(); - outputTest.it('should output a single object', ctx => { + outputTest.it('should output a single object', (ctx) => { ctx.testCmd.output({ foo: 'foo', bar: 'bar' }); expect(ctx.stdout).to.contain('Foo Bar\nfoo bar'); }); - outputTest.it('should output an array of objects', ctx => { - ctx.testCmd.output([{ foo: 'foo', bar: 'bar' }, { foo: '2', bar: '2' }]); + outputTest.it('should output an array of objects', (ctx) => { + ctx.testCmd.output([ + { foo: 'foo', bar: 'bar' }, + { foo: '2', bar: '2' }, + ]); expect(ctx.stdout).to.contain('Foo Bar\nfoo bar\n2 2'); }); - outputTest.it('should output requested properties', ctx => { - ctx.testCmd.output([{ foo: 'foo', bar: 'bar', baz: 'baz' }, { foo: '2', bar: '2', baz: '2' }], 'foo, bar'); + outputTest.it('should output requested properties', (ctx) => { + ctx.testCmd.output( + [ + { foo: 'foo', bar: 'bar', baz: 'baz' }, + { foo: '2', bar: '2', baz: '2' }, + ], + 'foo, bar', + ); expect(ctx.stdout).to.contain('Foo Bar\nfoo bar\n2 2'); }); - outputTest.stderr().it('should warn if invalid property name passed', ctx => { - ctx.testCmd.output([{ foo: 'foo', bar: 'bar', baz: 'baz' }, { foo: '2', bar: '2', baz: '2' }], 'foo, barn'); + outputTest.stderr().it('should warn if invalid property name passed', (ctx) => { + ctx.testCmd.output( + [ + { foo: 'foo', bar: 'bar', baz: 'baz' }, + { foo: '2', bar: '2', baz: '2' }, + ], + 'foo, barn', + ); expect(ctx.stdout).to.contain('Foo\nfoo\n2'); expect(ctx.stderr).to.contain('"barn" is not a valid property name.'); }); - outputTest.it('should output requested object properties', ctx => { - ctx.testCmd.output([ - { foo: 'foo', bar: { baz: 1, boz: 2 } }, - { foo: '2', bar: { baz: 3, boz: 'four' } } - ], 'foo, bar'); + outputTest.it('should output requested object properties', (ctx) => { + ctx.testCmd.output( + [ + { foo: 'foo', bar: { baz: 1, boz: 2 } }, + { foo: '2', bar: { baz: 3, boz: 'four' } }, + ], + 'foo, bar', + ); expect(ctx.stdout).to.contain('foo {"baz":1,"boz":2}'); expect(ctx.stdout).to.contain('2 {"baz":3,"boz":"four"}'); }); - outputTest.stderr().it('should output a message when the array is empty', ctx => { + outputTest.stderr().it('should output a message when the array is empty', (ctx) => { ctx.testCmd.output([]); expect(ctx.stdout).to.be.empty; expect(ctx.stderr).to.contain('No results'); @@ -149,13 +176,16 @@ describe('base-commands', () => { test .twilioCliEnv(Config) - .do(async ctx => { + .do(async (ctx) => { ctx.testCmd = new BaseCommand(['-o', 'json'], ctx.fakeConfig); await ctx.testCmd.run(); }) .stdout() - .it('should output an array of objects as JSON', ctx => { - const testData = [{ foo: 'foo', bar: 'bar' }, { foo: '2', bar: '2' }]; + .it('should output an array of objects as JSON', (ctx) => { + const testData = [ + { foo: 'foo', bar: 'bar' }, + { foo: '2', bar: '2' }, + ]; ctx.testCmd.output(testData); const outputObject = JSON.parse(ctx.stdout); expect(outputObject[0].foo).to.equal(testData[0].foo); @@ -163,20 +193,23 @@ describe('base-commands', () => { test .twilioCliEnv(Config) - .do(async ctx => { + .do(async (ctx) => { ctx.testCmd = new BaseCommand(['-o', 'tsv'], ctx.fakeConfig); await ctx.testCmd.run(); }) .stdout() - .it('should output an array of objects as TSV', ctx => { - const testData = [{ FOO: 'foo', BAR: 'bar' }, { FOO: '2', BAR: '2' }]; + .it('should output an array of objects as TSV', (ctx) => { + const testData = [ + { FOO: 'foo', BAR: 'bar' }, + { FOO: '2', BAR: '2' }, + ]; ctx.testCmd.output(testData); expect(ctx.stdout).to.contain('FOO\tBAR\nfoo\tbar\n2\t2'); }); }); describe('getPromptMessage', () => { - baseCommandTest.it('adds a colon to the end of the message', ctx => { + baseCommandTest.it('adds a colon to the end of the message', (ctx) => { expect(ctx.testCmd.getPromptMessage('Name: ')).to.equal('Name:'); expect(ctx.testCmd.getPromptMessage('Number.')).to.equal('Number:'); expect(ctx.testCmd.getPromptMessage(' Address ')).to.equal('Address:'); diff --git a/test/base-commands/twilio-client-command.test.js b/test/base-commands/twilio-client-command.test.js index fe86c419..1bf82702 100644 --- a/test/base-commands/twilio-client-command.test.js +++ b/test/base-commands/twilio-client-command.test.js @@ -1,12 +1,13 @@ +/* eslint-disable max-classes-per-file */ const { expect, test, constants } = require('@twilio/cli-test'); + const TwilioClientCommand = require('../../src/base-commands/twilio-client-command'); const { Config, ConfigData } = require('../../src/services/config'); const { TwilioCliError } = require('../../src/services/error'); describe('base-commands', () => { describe('twilio-client-command', () => { - class TestClientCommand extends TwilioClientCommand { - } + class TestClientCommand extends TwilioClientCommand {} class ThrowingUnknownClientCommand extends TwilioClientCommand { async run() { @@ -24,13 +25,12 @@ describe('base-commands', () => { } } - class AccountSidClientCommand extends TwilioClientCommand { - } + class AccountSidClientCommand extends TwilioClientCommand {} TestClientCommand.flags = TwilioClientCommand.flags; ThrowingUnknownClientCommand.flags = TwilioClientCommand.flags; Throwing20003ClientCommand.flags = TwilioClientCommand.flags; - AccountSidClientCommand.flags = Object.assign({}, TwilioClientCommand.flags, TwilioClientCommand.accountSidFlag); + AccountSidClientCommand.flags = { ...TwilioClientCommand.flags, ...TwilioClientCommand.accountSidFlag }; const setUpTest = ( args = [], @@ -38,11 +38,14 @@ describe('base-commands', () => { setUpUserConfig = undefined, mockSecureStorage = true, commandClass: CommandClass = TestClientCommand, - envRegion, envEdge, configRegion = 'configRegion', configEdge - } = {} + envRegion, + envEdge, + configRegion = 'configRegion', + configEdge, + } = {}, ) => { return test - .do(ctx => { + .do((ctx) => { ctx.userConfig = new ConfigData(); ctx.userConfig.edge = configEdge; @@ -65,17 +68,16 @@ describe('base-commands', () => { }) .twilioCliEnv(Config) .stderr() - .do(async ctx => { + .do(async (ctx) => { ctx.testCmd = new CommandClass(args, ctx.fakeConfig); - ctx.testCmd.secureStorage = - { - async getCredentials(profileId) { - return { - apiKey: mockSecureStorage ? constants.FAKE_API_KEY : 'error', - apiSecret: constants.FAKE_API_SECRET + profileId - }; - } - }; + ctx.testCmd.secureStorage = { + async getCredentials(profileId) { + return { + apiKey: mockSecureStorage ? constants.FAKE_API_KEY : 'error', + apiSecret: constants.FAKE_API_SECRET + profileId, + }; + }, + }; // This is essentially what oclif does behind the scenes. try { @@ -86,30 +88,29 @@ describe('base-commands', () => { }); }; - setUpTest(['-l', 'debug']).it('should create a client for the active profile', ctx => { + setUpTest(['-l', 'debug']).it('should create a client for the active profile', (ctx) => { expect(ctx.stderr).to.contain('MyFirstProfile'); expect(ctx.testCmd.twilioClient.accountSid).to.equal(constants.FAKE_ACCOUNT_SID); expect(ctx.testCmd.twilioClient.username).to.equal(constants.FAKE_API_KEY); - expect(ctx.testCmd.twilioClient.password).to.equal(constants.FAKE_API_SECRET + 'MyFirstProfile'); + expect(ctx.testCmd.twilioClient.password).to.equal(`${constants.FAKE_API_SECRET}MyFirstProfile`); expect(ctx.testCmd.twilioClient.region).to.equal(undefined); expect(ctx.testCmd.twilioClient.edge).to.equal(undefined); }); - setUpTest(['-l', 'debug', '--account-sid', 'ACbaccbaccbaccbaccbaccbaccbaccbacc'], { commandClass: AccountSidClientCommand }).it( - 'should create a client for the active profile with a different account SID', - ctx => { - expect(ctx.stderr).to.contain('MyFirstProfile'); - expect(ctx.testCmd.twilioClient.accountSid).to.equal('ACbaccbaccbaccbaccbaccbaccbaccbacc'); - expect(ctx.testCmd.twilioClient.username).to.equal(constants.FAKE_API_KEY); - expect(ctx.testCmd.twilioClient.password).to.equal(constants.FAKE_API_SECRET + 'MyFirstProfile'); - expect(ctx.testCmd.twilioClient.region).to.equal(undefined); - expect(ctx.testCmd.twilioClient.edge).to.equal(undefined); - } - ); + setUpTest(['-l', 'debug', '--account-sid', 'ACbaccbaccbaccbaccbaccbaccbaccbacc'], { + commandClass: AccountSidClientCommand, + }).it('should create a client for the active profile with a different account SID', (ctx) => { + expect(ctx.stderr).to.contain('MyFirstProfile'); + expect(ctx.testCmd.twilioClient.accountSid).to.equal('ACbaccbaccbaccbaccbaccbaccbaccbacc'); + expect(ctx.testCmd.twilioClient.username).to.equal(constants.FAKE_API_KEY); + expect(ctx.testCmd.twilioClient.password).to.equal(`${constants.FAKE_API_SECRET}MyFirstProfile`); + expect(ctx.testCmd.twilioClient.region).to.equal(undefined); + expect(ctx.testCmd.twilioClient.edge).to.equal(undefined); + }); setUpTest(['-l', 'debug'], { setUpUserConfig: () => 0 }) .exit(1) - .it('should fail for a non-existent active profile', ctx => { + .it('should fail for a non-existent active profile', (ctx) => { expect(ctx.stderr).to.contain('Could not find profile.'); expect(ctx.stderr).to.contain('To create the profile, run:'); expect(ctx.stderr).to.contain('twilio profiles:create'); @@ -118,23 +119,23 @@ describe('base-commands', () => { setUpTest(['-p', 'alt', '-l', 'debug']) .exit(1) - .it('should fail for a non-existent profile', ctx => { + .it('should fail for a non-existent profile', (ctx) => { expect(ctx.stderr).to.contain('Could not find profile "alt".'); expect(ctx.stderr).to.contain('To create the profile, run:'); expect(ctx.stderr).to.contain('twilio profiles:create --profile "alt"'); expect(ctx.stderr).to.contain('TWILIO_ACCOUNT_SID'); }); - setUpTest(['-p', 'region-edge-testing']).it('should create a client for a non-default profile', ctx => { + setUpTest(['-p', 'region-edge-testing']).it('should create a client for a non-default profile', (ctx) => { expect(ctx.testCmd.twilioClient.accountSid).to.equal(constants.FAKE_ACCOUNT_SID); expect(ctx.testCmd.twilioClient.username).to.equal(constants.FAKE_API_KEY); - expect(ctx.testCmd.twilioClient.password).to.equal(constants.FAKE_API_SECRET + 'region-edge-testing'); + expect(ctx.testCmd.twilioClient.password).to.equal(`${constants.FAKE_API_SECRET}region-edge-testing`); expect(ctx.testCmd.twilioClient.region).to.equal('configRegion'); }); setUpTest(['-p', 'region-edge-testing'], { mockSecureStorage: false }) .exit(1) - .it('should handle a secure storage error', ctx => { + .it('should handle a secure storage error', (ctx) => { expect(ctx.stderr).to.contain('Could not get credentials for profile "region-edge-testing"'); expect(ctx.stderr).to.contain('To reconfigure the profile, run:'); expect(ctx.stderr).to.contain('twilio profiles:create --profile "region-edge-testing"'); @@ -142,48 +143,48 @@ describe('base-commands', () => { setUpTest([], { commandClass: ThrowingUnknownClientCommand }) .exit(1) - .it('should catch unhandled errors', ctx => { + .it('should catch unhandled errors', (ctx) => { expect(ctx.stderr).to.contain('unexpected error'); }); setUpTest([], { commandClass: Throwing20003ClientCommand }) .exit(20003) - .it('should catch access denied errors and enhance the message', ctx => { + .it('should catch access denied errors and enhance the message', (ctx) => { expect(ctx.stderr).to.contain('Access Denied'); expect(ctx.stderr).to.contain('Standard API Keys'); }); setUpTest([], { commandClass: Throwing20003ClientCommand, envRegion: 'region' }) .exit(20003) - .it('should catch access denied errors but not enhance the message when using env var auth', ctx => { + .it('should catch access denied errors but not enhance the message when using env var auth', (ctx) => { expect(ctx.stderr).to.contain('Access Denied'); expect(ctx.stderr).to.not.contain('Standard API Keys'); }); describe('parseProperties', () => { - setUpTest().it('should ignore empty PropertyFlags', ctx => { + setUpTest().it('should ignore empty PropertyFlags', (ctx) => { const updatedProperties = ctx.testCmd.parseProperties(); expect(updatedProperties).to.be.null; }); - setUpTest().it('should ignore missing command flags', ctx => { + setUpTest().it('should ignore missing command flags', (ctx) => { ctx.testCmd.constructor.PropertyFlags = { 'friendly-name': {}, - 'sms-url': {} + 'sms-url': {}, }; const updatedProperties = ctx.testCmd.parseProperties(); expect(updatedProperties).to.be.null; }); - setUpTest().it('should parse options into API resource properties', ctx => { + setUpTest().it('should parse options into API resource properties', (ctx) => { ctx.testCmd.constructor.PropertyFlags = { 'friendly-name': {}, - 'sms-url': {} + 'sms-url': {}, }; ctx.testCmd.flags = { 'friendly-name': 'Casper', - 'sms-url': 'https://localhost:5000/sms' + 'sms-url': 'https://localhost:5000/sms', }; const updatedProperties = ctx.testCmd.parseProperties(); @@ -191,7 +192,7 @@ describe('base-commands', () => { expect(updatedProperties.smsUrl).to.equal('https://localhost:5000/sms'); }); - setUpTest().it('should parse empty command flags', ctx => { + setUpTest().it('should parse empty command flags', (ctx) => { ctx.testCmd.constructor.PropertyFlags = { 'sms-url': {} }; ctx.testCmd.flags = { 'sms-url': '' }; @@ -201,7 +202,7 @@ describe('base-commands', () => { }); describe('updateResource', () => { - setUpTest().it('should return nothing to update if no properties passed', async ctx => { + setUpTest().it('should return nothing to update if no properties passed', async (ctx) => { const resourceSid = constants.FAKE_ACCOUNT_SID; const results = await ctx.testCmd.updateResource(null, resourceSid); expect(results.sid).to.equal(resourceSid); @@ -209,18 +210,18 @@ describe('base-commands', () => { expect(ctx.stderr).to.contain('Nothing to update'); }); - setUpTest().it('should return success if resource was updated', async ctx => { + setUpTest().it('should return success if resource was updated', async (ctx) => { const resourceSid = constants.FAKE_ACCOUNT_SID; const updatedProperties = { - friendlyName: 'Casper' + friendlyName: 'Casper', }; - const fakeResource = sid => { + const fakeResource = (sid) => { expect(sid).to.equal(resourceSid); return { async update(props) { expect(props).to.eql(updatedProperties); - } + }, }; }; @@ -229,24 +230,24 @@ describe('base-commands', () => { expect(results.result).to.equal('Success'); }); - setUpTest().it('should return success if resource was updated from flags', async ctx => { + setUpTest().it('should return success if resource was updated from flags', async (ctx) => { ctx.testCmd.constructor.PropertyFlags = { 'friendly-name': {}, - 'sms-url': {} + 'sms-url': {}, }; ctx.testCmd.flags = { 'friendly-name': 'Casper', - 'sms-url': 'https://localhost:5000/sms' + 'sms-url': 'https://localhost:5000/sms', }; const resourceSid = constants.FAKE_ACCOUNT_SID; - const fakeResource = sid => { + const fakeResource = (sid) => { expect(sid).to.equal(resourceSid); return { async update(props) { expect(props.friendlyName).to.equal('Casper'); expect(props.smsUrl).to.equal('https://localhost:5000/sms'); - } + }, }; }; @@ -255,15 +256,15 @@ describe('base-commands', () => { expect(results.result).to.equal('Success'); }); - setUpTest().it('should report an error if API call fails', async ctx => { + setUpTest().it('should report an error if API call fails', async (ctx) => { const resourceSid = constants.FAKE_ACCOUNT_SID; - const fakeResource = sid => { + const fakeResource = (sid) => { expect(sid).to.equal(resourceSid); return { async update() { const err = { message: 'A fake API error' }; throw err; - } + }, }; }; @@ -275,26 +276,28 @@ describe('base-commands', () => { }); describe('regional and edge support', () => { - setUpTest([], { configEdge: 'edge' }).it('should use the config edge when defined', ctx => { + setUpTest([], { configEdge: 'edge' }).it('should use the config edge when defined', (ctx) => { expect(ctx.testCmd.twilioApiClient.edge).to.equal('edge'); expect(ctx.testCmd.twilioApiClient.region).to.be.undefined; }); - setUpTest(['-p', 'region-edge-testing']).it('should use the config region when defined', ctx => { + setUpTest(['-p', 'region-edge-testing']).it('should use the config region when defined', (ctx) => { expect(ctx.testCmd.twilioApiClient.region).to.equal('configRegion'); expect(ctx.testCmd.twilioApiClient.edge).to.be.undefined; }); - setUpTest([], { envRegion: 'region' }).it('should use the env region over a config region', ctx => { + setUpTest([], { envRegion: 'region' }).it('should use the env region over a config region', (ctx) => { expect(ctx.testCmd.twilioApiClient.region).to.equal('region'); expect(ctx.testCmd.twilioApiClient.edge).to.be.undefined; }); - setUpTest([], { configEdge: 'configEdge', envEdge: 'edge', envRegion: 'region' }) - .it('should use the env edge over a config edge', ctx => { + setUpTest([], { configEdge: 'configEdge', envEdge: 'edge', envRegion: 'region' }).it( + 'should use the env edge over a config edge', + (ctx) => { expect(ctx.testCmd.twilioApiClient.edge).to.equal('edge'); expect(ctx.testCmd.twilioApiClient.region).to.equal('region'); - }); + }, + ); }); }); }); diff --git a/test/helpers/init.js b/test/helpers/init.js index 7f1bfb73..79136c6b 100644 --- a/test/helpers/init.js +++ b/test/helpers/init.js @@ -1,2 +1,3 @@ const path = require('path'); + process.env.TS_NODE_PROJECT = path.resolve('test/tsconfig.json'); diff --git a/test/services/api-schema.test.js b/test/services/api-schema.test.js index f2f1559b..ea2cc96f 100644 --- a/test/services/api-schema.test.js +++ b/test/services/api-schema.test.js @@ -1,4 +1,5 @@ const { expect, test } = require('@twilio/cli-test'); + const TwilioSchemaConverter = require('../../src/services/api-schema/twilio-converter'); const { logger, LoggingLevel } = require('../../src/services/messaging/logging'); @@ -13,16 +14,17 @@ describe('services', () => { logger.config.level = LoggingLevel.info; }); - test.stderr().it('handles dates, objects, and strings', ctx => { + test.stderr().it('handles dates, objects, and strings', (ctx) => { /* eslint-disable camelcase */ const schema = { - type: 'object', properties: { + type: 'object', + properties: { date_created: { type: 'string', format: 'date-time' }, date_updated: { type: 'string', format: 'date-time-rfc-2822' }, message_type: { type: 'array', items: { type: 'string' } }, free_form_obj: { type: 'object' }, - some_uri: { type: 'string', format: 'uri' } - } + some_uri: { type: 'string', format: 'uri' }, + }, }; const input = { @@ -30,7 +32,7 @@ describe('services', () => { date_updated: 'Mon, 15 Sep 2008 15:53:00 +0500', message_type: ['not', 'a', 'message'], free_form_obj: { first_key: 'first_value', second_key: 'second_value' }, - some_uri: '/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json' + some_uri: '/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json', }; const expected = { @@ -38,7 +40,7 @@ describe('services', () => { dateUpdated: new Date('September 15, 2008 15:53 GMT+0500'), messageType: ['not', 'a', 'message'], freeFormObj: { first_key: 'first_value', second_key: 'second_value' }, - someUri: '/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json' + someUri: '/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json', }; const actual = new TwilioSchemaConverter().convertSchema(schema, input); @@ -47,7 +49,7 @@ describe('services', () => { expect(ctx.stderr).to.be.empty; }); - test.stderr().it('handles unknown string formats', ctx => { + test.stderr().it('handles unknown string formats', (ctx) => { const schema = { type: 'string', format: 'price' }; const input = '$1.23'; const expected = input; @@ -58,7 +60,7 @@ describe('services', () => { expect(ctx.stderr).to.contain('price'); }); - test.stderr().it('handles poorly formatted dates', ctx => { + test.stderr().it('handles poorly formatted dates', (ctx) => { const schema = { type: 'string', format: 'date-time' }; const input = 'abc123'; const expected = input; @@ -69,7 +71,7 @@ describe('services', () => { expect(ctx.stderr).to.contain('date-time'); }); - test.stderr().it('handles unknown schemas', ctx => { + test.stderr().it('handles unknown schemas', (ctx) => { const schema = { type: 'bugs' }; const input = ['insects']; const expected = input; @@ -80,7 +82,7 @@ describe('services', () => { expect(ctx.stderr).to.contain('bugs'); }); - test.stderr().it('handles null values when nullable not allowed', ctx => { + test.stderr().it('handles null values when nullable not allowed', (ctx) => { const schema = { type: 'object' }; const input = null; const expected = input; @@ -91,7 +93,7 @@ describe('services', () => { expect(ctx.stderr).to.contain('nullable'); }); - test.stderr().it('handles null values when nullable is allowed', ctx => { + test.stderr().it('handles null values when nullable is allowed', (ctx) => { const schema = { type: 'array', nullable: true }; const input = null; const expected = input; diff --git a/test/services/cli-http-client.test.js b/test/services/cli-http-client.test.js index 9590d1fc..396b3d80 100644 --- a/test/services/cli-http-client.test.js +++ b/test/services/cli-http-client.test.js @@ -1,4 +1,5 @@ const { expect, test } = require('@twilio/cli-test'); + const CliRequestClient = require('../../src/services/cli-http-client'); const { TwilioCliError } = require('../../src/services/error'); const { Logger, LoggingLevel } = require('../../src/services/messaging/logging'); @@ -6,11 +7,11 @@ const { Logger, LoggingLevel } = require('../../src/services/messaging/logging') describe('services', () => { describe('cli-http-client', () => { const logger = new Logger({ - level: LoggingLevel.none + level: LoggingLevel.none, }); test.it('should make an http request', async () => { - const client = new CliRequestClient('blah', logger, options => { + const client = new CliRequestClient('blah', logger, (options) => { expect(options.url).to.equal('https://foo.com/bar'); return { status: 200, data: 'foo', headers: {} }; }); @@ -22,7 +23,7 @@ describe('services', () => { password: 'aaaaaaaaa', headers: { 'User-Agent': 'test' }, params: { x: 1, y: 2 }, - data: { foo: 'bar' } + data: { foo: 'bar' }, }); expect(response.statusCode).to.equal(200); @@ -30,7 +31,7 @@ describe('services', () => { }); test - .nock('https://foo.com', api => { + .nock('https://foo.com', (api) => { api.get('/bar').delay(100).reply(200); }) .it('throws a TwilioCliError on response timeouts', async () => { @@ -40,7 +41,7 @@ describe('services', () => { }); test - .nock('https://foo.com', api => { + .nock('https://foo.com', (api) => { api.get('/bar').replyWithError({ code: 'ETIMEDOUT' }); }) .it('throws a TwilioCliError on connection timeouts', async () => { @@ -50,7 +51,7 @@ describe('services', () => { }); test - .nock('https://foo.com', api => { + .nock('https://foo.com', (api) => { api.get('/bar?foo=bar&foo=baz').reply(200); }) .it('correctly serializes array parameters', async () => { @@ -59,8 +60,8 @@ describe('services', () => { method: 'GET', uri: 'https://foo.com/bar', params: { - foo: ['bar', 'baz'] - } + foo: ['bar', 'baz'], + }, }); expect(response.statusCode).to.equal(200); }); diff --git a/test/services/config.test.js b/test/services/config.test.js index d89f2fd3..e38bc9d5 100644 --- a/test/services/config.test.js +++ b/test/services/config.test.js @@ -1,7 +1,8 @@ -const tmp = require('tmp'); const path = require('path'); +const tmp = require('tmp'); const { expect, test, constants } = require('@twilio/cli-test'); + const { Config, ConfigData } = require('../../src/services/config'); const FAKE_AUTH_TOKEN = '1234567890abcdefghijklmnopqrstuvwxyz'; @@ -158,7 +159,7 @@ describe('services', () => { configData.addProfile('thirdProfile', 'newest_account_SID'); const fakeProfile = { id: 'DOES_NOT_EXIST', - accountSid: 'fake_SID' + accountSid: 'fake_SID', }; const originalLength = configData.profiles.length; configData.removeProfile(fakeProfile); diff --git a/test/services/env.test.js b/test/services/env.test.js index f370504a..81ca9539 100644 --- a/test/services/env.test.js +++ b/test/services/env.test.js @@ -2,9 +2,10 @@ const os = require('os'); const path = require('path'); const { expect, test } = require('@twilio/cli-test'); + const configureEnv = require('../../src/services/env'); -const ORIGINAL_ENV = Object.assign({}, process.env); +const ORIGINAL_ENV = { ...process.env }; const DEFAULT_DIR = path.join('home', '.twilio-cli'); describe('services', () => { diff --git a/test/services/javascript-utilities.test.js b/test/services/javascript-utilities.test.js index 620d2480..a7979350 100644 --- a/test/services/javascript-utilities.test.js +++ b/test/services/javascript-utilities.test.js @@ -1,7 +1,15 @@ -const { doesObjectHaveProperty, translateKeys, translateValues, sleep, splitArray, instanceOf } = require('../../src/services/javascript-utilities'); - +/* eslint-disable max-classes-per-file */ const { expect, test } = require('@twilio/cli-test'); +const { + doesObjectHaveProperty, + translateKeys, + translateValues, + sleep, + splitArray, + instanceOf, +} = require('../../src/services/javascript-utilities'); + describe('services', () => { describe('javascript-utilities', () => { describe('doesObjectHaveProperty', () => { @@ -32,7 +40,7 @@ describe('services', () => { }); describe('translateKeys', () => { - const keyFunc = key => key.toUpperCase(); + const keyFunc = (key) => key.toUpperCase(); test.it('should translate the keys of a complex object', () => { const actual = { @@ -45,8 +53,8 @@ describe('services', () => { three: 3, toJSON() { return { one: this.one, two: this.two }; - } - } + }, + }, }; const expected = { @@ -55,8 +63,8 @@ describe('services', () => { NESTED: { LEVEL2: { LEVEL3: 'value' } }, CUSTOM: { ONE: 1, - TWO: 2 - } + TWO: 2, + }, }; expect(translateKeys(actual, keyFunc)).to.eql(expected); @@ -64,7 +72,7 @@ describe('services', () => { }); describe('translateValues', () => { - const valueFunc = key => key.toUpperCase(); + const valueFunc = (key) => key.toUpperCase(); test.it('should translate the values of a complex object', () => { const actual = { @@ -77,8 +85,8 @@ describe('services', () => { three: 'thr33', toJSON() { return { one: this.one, two: this.two }; - } - } + }, + }, }; const expected = { @@ -87,8 +95,8 @@ describe('services', () => { nested: { level2: { level3: 'VALUE' } }, custom: { one: 'WON', - two: 'TOO' - } + two: 'TOO', + }, }; expect(translateValues(actual, valueFunc)).to.eql(expected); @@ -112,7 +120,7 @@ describe('services', () => { describe('splitArray', () => { test.it('should split the array in 2', () => { const testArray = ['a', 'ey!', 'bee', 'b', 'c', 'SEA']; - const isLengthOne = item => item.length === 1; + const isLengthOne = (item) => item.length === 1; const [matched, notMatched] = splitArray(testArray, isLengthOne); diff --git a/test/services/messaging/logging.test.js b/test/services/messaging/logging.test.js index f3071777..90e6f8d5 100644 --- a/test/services/messaging/logging.test.js +++ b/test/services/messaging/logging.test.js @@ -1,15 +1,16 @@ const { expect, test } = require('@twilio/cli-test'); + const { logger } = require('../../../src/services/messaging/logging'); describe('services', () => { describe('messaging', () => { describe('logging.logger', () => { - test.stderr().it('should at least log errors by default', ctx => { + test.stderr().it('should at least log errors by default', (ctx) => { logger.error('heir or?'); expect(ctx.stderr).to.not.be.empty; }); - test.stderr().it('should not log debug by default', ctx => { + test.stderr().it('should not log debug by default', (ctx) => { logger.debug('eek!'); expect(ctx.stderr).to.be.empty; }); diff --git a/test/services/messaging/templating.test.js b/test/services/messaging/templating.test.js index 70612caa..1aaad28e 100644 --- a/test/services/messaging/templating.test.js +++ b/test/services/messaging/templating.test.js @@ -1,4 +1,5 @@ const { expect, test } = require('@twilio/cli-test'); + const { templatize } = require('../../../src/services/messaging/templating'); const EXPECTED_MESSAGE = 'Indiana Jones and the Template of Doom'; diff --git a/test/services/naming-conventions.test.js b/test/services/naming-conventions.test.js index 489e04f8..7da04e2c 100644 --- a/test/services/naming-conventions.test.js +++ b/test/services/naming-conventions.test.js @@ -1,7 +1,7 @@ -const { kebabCase, camelCase, pascalCase, snakeCase, capitalize } = require('../../src/services/naming-conventions'); - const { expect, test } = require('@twilio/cli-test'); +const { kebabCase, camelCase, pascalCase, snakeCase, capitalize } = require('../../src/services/naming-conventions'); + describe('services', () => { describe('namingConventions', () => { test.it('handles single word', () => { diff --git a/test/services/require-install.test.js b/test/services/require-install.test.js index ae812187..480b3ecd 100644 --- a/test/services/require-install.test.js +++ b/test/services/require-install.test.js @@ -1,39 +1,53 @@ const tmp = require('tmp'); const { expect, test } = require('@twilio/cli-test'); -const { getCommandPlugin, getPackageVersion, getDependencyVersion, checkVersion, requireInstall } = require('../../src/services/require-install'); + +const { TwilioCliError } = require('../../src/services/error'); +const { + getCommandPlugin, + getPackageVersion, + getDependencyVersion, + checkVersion, + requireInstall, +} = require('../../src/services/require-install'); const { logger, LoggingLevel } = require('../../src/services/messaging/logging'); const corePJSON = require('../../package.json'); const TOP_PLUGIN = { name: 'top-plugin', options: {}, - commands: [{ - id: 'top-command', - aliases: [] - }] + commands: [ + { + id: 'top-command', + aliases: [], + }, + ], }; const DYNAMIC_PLUGIN = { name: 'dynamic-plugin', options: { name: 'top-plugin' }, - commands: [{ - id: 'dynamic-command', - aliases: [] - }] + commands: [ + { + id: 'dynamic-command', + aliases: [], + }, + ], }; const INSTALLED_PLUGIN = { name: 'installed-plugin', options: { name: 'installed-plugin' }, - commands: [{ - id: 'installed-command', - aliases: ['alias-installed-command'] - }] + commands: [ + { + id: 'installed-command', + aliases: ['alias-installed-command'], + }, + ], }; const config = { plugins: [TOP_PLUGIN, DYNAMIC_PLUGIN, INSTALLED_PLUGIN], - dataDir: tmp.dirSync({ unsafeCleanup: true }).name + dataDir: tmp.dirSync({ unsafeCleanup: true }).name, }; /* eslint-disable max-nested-callbacks */ @@ -62,7 +76,7 @@ describe('services', () => { test.it('handles unknown commands', () => { const command = { id: 'what-command', config }; - expect(getCommandPlugin(command)).to.be.undefined; + expect(() => getCommandPlugin(command)).to.throw(TwilioCliError); }); }); @@ -87,7 +101,7 @@ describe('services', () => { test.it('can retrieve dependency versions', () => { const pjson = { dependencies: { keytar: '1.2.3' }, - optionalDependencies: { tartar: '4.5.6' } + optionalDependencies: { tartar: '4.5.6' }, }; expect(getDependencyVersion('keytar', pjson)).to.equal('1.2.3'); @@ -108,7 +122,7 @@ describe('services', () => { expect(requireInstall('chai')).to.not.be.undefined; }); - test.stderr().it('will attempt to install packages', async ctx => { + test.stderr().it('will attempt to install packages', async (ctx) => { const command = { id: 'top-command', config }; await expect(requireInstall('chai-dai', command)).to.be.rejected; expect(ctx.stderr).to.contain('Installing chai-dai'); diff --git a/test/services/require-native.test.js b/test/services/require-native.test.js deleted file mode 100644 index 39c3b745..00000000 --- a/test/services/require-native.test.js +++ /dev/null @@ -1,14 +0,0 @@ -const { expect, test } = require('@twilio/cli-test'); -const { requireNative } = require('../../src/services/require-native'); - -describe('services', () => { - describe('require-native', () => { - test.it('can load modules', () => { - expect(requireNative('chai')).to.not.be.undefined; - }); - - test.it('does not throw for unknown modules', () => { - expect(requireNative('chai-dai')).to.be.undefined; - }); - }); -}); diff --git a/test/services/secure-storage.test.js b/test/services/secure-storage.test.js index 9b712e6e..121f2ef7 100644 --- a/test/services/secure-storage.test.js +++ b/test/services/secure-storage.test.js @@ -1,4 +1,5 @@ const { expect, test } = require('@twilio/cli-test'); + const { SecureStorage, STORAGE_LOCATIONS } = require('../../src/services/secure-storage'); const { TwilioCliError } = require('../../src/services/error'); @@ -30,32 +31,30 @@ describe('services', () => { }); describe('getCredentials', () => { - const setup = getPassword => test - .do(ctx => { + const setup = (getPassword) => + test.do((ctx) => { ctx.secureStorage = new SecureStorage(); Object.defineProperty(ctx.secureStorage, 'keytar', { - get: () => ({ getPassword }) + get: () => ({ getPassword }), }); }); - setup(async () => 'key|password') - .it('handles a key-password pair', async ctx => { - const expected = { apiKey: 'key', apiSecret: 'password' }; - expect(await ctx.secureStorage.getCredentials('Pro File')).to.eql(expected); - }); + setup(async () => 'key|password').it('handles a key-password pair', async (ctx) => { + const expected = { apiKey: 'key', apiSecret: 'password' }; + expect(await ctx.secureStorage.getCredentials('Pro File')).to.eql(expected); + }); setup(async () => { throw new Error('WHOA!'); - }) - .it('handles a keytar error', async ctx => { - const expected = { apiKey: 'error', apiSecret: 'WHOA!' }; - expect(await ctx.secureStorage.getCredentials('No File')).to.eql(expected); - }); + }).it('handles a keytar error', async (ctx) => { + const expected = { apiKey: 'error', apiSecret: 'WHOA!' }; + expect(await ctx.secureStorage.getCredentials('No File')).to.eql(expected); + }); setup(async () => { throw new TwilioCliError('WOE!'); }) - .do(ctx => ctx.secureStorage.getCredentials('Woe File')) + .do((ctx) => ctx.secureStorage.getCredentials('Woe File')) .catch(/WOE!/) .it('passes a TwilioCliError error through'); }); diff --git a/test/services/twilio-api/api-browser.test.js b/test/services/twilio-api/api-browser.test.js index 94ded8f2..eb4b5204 100644 --- a/test/services/twilio-api/api-browser.test.js +++ b/test/services/twilio-api/api-browser.test.js @@ -1,7 +1,7 @@ -const { TwilioApiBrowser } = require('../../../src/services/twilio-api'); - const { expect, test } = require('@twilio/cli-test'); +const { TwilioApiBrowser } = require('../../../src/services/twilio-api'); + describe('services', () => { describe('twilio-api', () => { describe('TwilioApiBrowser', () => { @@ -19,62 +19,62 @@ describe('services', () => { '/2010-04-01/Widgets.json': { servers: [ { - url: 'https://api.twilio.com' - } + url: 'https://api.twilio.com', + }, ], description: 'Widgets here\nsecond line of text', - 'x-default-output-properties': ['sid'] - } + 'x-default-output-properties': ['sid'], + }, }, tags: [ { name: 'Beta', - description: 'Betamax!' - } - ] + description: 'Betamax!', + }, + ], }, neato: { paths: { '/v1/Gadgets.json': { servers: [ { - url: 'https://neato.twilio.com' - } + url: 'https://neato.twilio.com', + }, ], description: 'v1 Gadgets here', - 'x-default-output-properties': ['sid'] + 'x-default-output-properties': ['sid'], }, '/v2/Gadgets.json': { servers: [ { - url: 'https://neato.twilio.com' - } + url: 'https://neato.twilio.com', + }, ], post: { createStuff: '' }, get: { listStuff: '' }, description: 'v2 list Gadgets here', - 'x-default-output-properties': ['sid', 'name'] + 'x-default-output-properties': ['sid', 'name'], }, '/v2/Gadgets/{Sid}.json': { servers: [ { - url: 'https://neato.twilio.com' - } + url: 'https://neato.twilio.com', + }, ], post: { updateStuff: '' }, get: { fetchStuff: '' }, delete: { removeStuff: '' }, description: 'v2 instance Gadgets here', - 'x-default-output-properties': ['sid', 'description'] - } + 'x-default-output-properties': ['sid', 'description'], + }, }, tags: [ { name: 'GA', - description: 'Generally Available!' - } - ] - } + description: 'Generally Available!', + }, + ], + }, }); expect(browser.domains).to.deep.equal({ @@ -82,53 +82,53 @@ describe('services', () => { tags: [ { name: 'Beta', - description: 'Betamax!' - } + description: 'Betamax!', + }, ], paths: { '/2010-04-01/Widgets.json': { operations: {}, server: 'https://api.twilio.com', description: 'Widgets here second line of text', - defaultOutputProperties: ['sid'] - } - } + defaultOutputProperties: ['sid'], + }, + }, }, neato: { tags: [ { name: 'GA', - description: 'Generally Available!' - } + description: 'Generally Available!', + }, ], paths: { '/v1/Gadgets.json': { operations: {}, server: 'https://neato.twilio.com', description: 'v1 Gadgets here', - defaultOutputProperties: ['sid'] + defaultOutputProperties: ['sid'], }, '/v2/Gadgets.json': { operations: { post: { createStuff: '' }, - get: { listStuff: '' } + get: { listStuff: '' }, }, server: 'https://neato.twilio.com', description: 'v2 list Gadgets here', - defaultOutputProperties: ['sid', 'name'] + defaultOutputProperties: ['sid', 'name'], }, '/v2/Gadgets/{Sid}.json': { operations: { get: { fetchStuff: '' }, post: { updateStuff: '' }, - delete: { removeStuff: '' } + delete: { removeStuff: '' }, }, server: 'https://neato.twilio.com', description: 'v2 instance Gadgets here', - defaultOutputProperties: ['sid', 'description'] - } - } - } + defaultOutputProperties: ['sid', 'description'], + }, + }, + }, }); }); }); diff --git a/test/services/twilio-api/twilio-client.test.js b/test/services/twilio-api/twilio-client.test.js index f5d4d0b4..a868306c 100644 --- a/test/services/twilio-api/twilio-client.test.js +++ b/test/services/twilio-api/twilio-client.test.js @@ -1,8 +1,9 @@ const { expect, test, constants } = require('@twilio/cli-test'); +const qs = require('qs'); + const { logger } = require('../../../src/services/messaging/logging'); const { TwilioApiClient } = require('../../../src/services/twilio-api'); const CliRequestClient = require('../../../src/services/cli-http-client'); -const qs = require('qs'); const accountSid = constants.FAKE_ACCOUNT_SID; const callSid = 'CA12345678901234567890123456789012'; @@ -12,106 +13,118 @@ describe('services', () => { describe('twilio-api', () => { describe('twilio-client', () => { const httpClient = new CliRequestClient('command-id', logger); - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient } - ); + const apiClient = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { /* eslint-disable camelcase */ api.get(`/2010-04-01/Accounts/${accountSid}/Calls.json?StartTime=${callStartTime}`).reply(200, { - calls: [{ - sid: callSid - }], - next_page_uri: '/nextPageOfResults' + calls: [ + { + sid: callSid, + }, + ], + next_page_uri: '/nextPageOfResults', }); api.get('/nextPageOfResults').reply(200, { - calls: [{ - sid: callSid - }], - next_page_uri: null + calls: [ + { + sid: callSid, + }, + ], + next_page_uri: null, }); /* eslint-enable camelcase */ }) .it('can list resources', async () => { - const response = await client.list({ + const response = await apiClient.list({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Calls.json', - data: { StartTime: callStartTime } + data: { StartTime: callStartTime }, }); expect(response).to.eql([{ sid: callSid }, { sid: callSid }]); }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { /* eslint-disable camelcase */ api.get(`/2010-04-01/Accounts/${accountSid}/Calls.json?PageSize=1`).reply(200, { - calls: [{ - sid: callSid - }], - next_page_uri: '/nextPageOfResults' + calls: [ + { + sid: callSid, + }, + ], + next_page_uri: '/nextPageOfResults', }); api.get('/nextPageOfResults').reply(200, { - calls: [{ - sid: callSid - }], - next_page_uri: '/nextPageOfResults' + calls: [ + { + sid: callSid, + }, + ], + next_page_uri: '/nextPageOfResults', }); /* eslint-enable camelcase */ }) .it('can list resources with page and limit restrictions', async () => { - const response = await client.list({ + const response = await apiClient.list({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Calls.json', - data: { Limit: 2, PageSize: 1 } + data: { Limit: 2, PageSize: 1 }, }); expect(response).to.eql([{ sid: callSid }, { sid: callSid }]); }); test - .nock('https://studio.twilio.com', api => { + .nock('https://studio.twilio.com', (api) => { /* eslint-disable camelcase */ api.get('/v1/Flows').reply(200, { - flows: [{ - friendly_name: 'drizzle' - }], + flows: [ + { + friendly_name: 'drizzle', + }, + ], meta: { - next_page_url: 'https://studio.twilio.com/nextPageOfResults' - } + next_page_url: 'https://studio.twilio.com/nextPageOfResults', + }, }); api.get('/nextPageOfResults').reply(200, { - flows: [{ - friendly_name: 'trickle' - }], + flows: [ + { + friendly_name: 'trickle', + }, + ], meta: { - next_page_url: null - } + next_page_url: null, + }, }); /* eslint-enable camelcase */ }) .it('can list resources with metadata', async () => { - const response = await client.list({ + const response = await apiClient.list({ domain: 'studio', - path: '/v1/Flows' + path: '/v1/Flows', }); expect(response).to.eql([{ friendlyName: 'drizzle' }, { friendlyName: 'trickle' }]); }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { api.post(`/2010-04-01/Accounts/${accountSid}/Calls.json`).reply(201, { - status: 'ringing' + status: 'ringing', }); }) .it('can create resources', async () => { - const response = await client.create({ + const response = await apiClient.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Calls.json', - data: { To: '123', From: '456', Junk: 'disregard' } + data: { To: '123', From: '456', Junk: 'disregard' }, }); expect(response).to.eql({ status: 'ringing' }); @@ -119,33 +132,33 @@ describe('services', () => { }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { api.get(`/2010-04-01/Accounts/${accountSid}/Calls/${callSid}.json`).reply(200, { - status: 'in-progress' + status: 'in-progress', }); }) .it('can fetch resources', async () => { - const response = await client.fetch({ + const response = await apiClient.fetch({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Calls/{Sid}.json', - pathParams: { Sid: callSid } + pathParams: { Sid: callSid }, }); expect(response).to.eql({ status: 'in-progress' }); }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { api.post(`/2010-04-01/Accounts/${accountSid}/Calls/${callSid}.json`).reply(200, { - status: 'canceled' + status: 'canceled', }); }) .it('can update resources', async () => { - const response = await client.update({ + const response = await apiClient.update({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Calls/{Sid}.json', pathParams: { Sid: callSid }, - data: { Status: 'canceled' } + data: { Status: 'canceled' }, }); expect(response).to.eql({ status: 'canceled' }); @@ -153,79 +166,87 @@ describe('services', () => { }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { api.delete(`/2010-04-01/Accounts/${accountSid}/Calls/${callSid}.json`).reply(204); }) .it('can remove resources', async () => { - const response = await client.remove({ + const response = await apiClient.remove({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Calls/{Sid}.json', - pathParams: { Sid: callSid } + pathParams: { Sid: callSid }, }); expect(response).to.be.true; }); test - .nock('https://api.dev.twilio.com', api => { + .nock('https://api.dev.twilio.com', (api) => { api.post(`/2010-04-01/Accounts/${accountSid}/Messages.json`).reply(201, { - status: 'queued' + status: 'queued', }); }) .it('handles other regions', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, region: 'dev' } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + region: 'dev', + }); const response = await client.create({ domain: 'api', - path: '/2010-04-01/Accounts/{AccountSid}/Messages.json' + path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', }); expect(response).to.eql({ status: 'queued' }); }); test - .nock('https://api.twilio.com', api => { - api.post(`/2010-04-01/Accounts/${accountSid}/Messages.json`).reply(401, - '{"code": 20003, "message": "Authenticate", "more_info": "https://www.twilio.com/docs/errors/20003"}' - ); + .nock('https://api.twilio.com', (api) => { + api + .post(`/2010-04-01/Accounts/${accountSid}/Messages.json`) + .reply( + 401, + '{"code": 20003, "message": "Authenticate", "more_info": "https://www.twilio.com/docs/errors/20003"}', + ); }) .it('handles request errors', async () => { - await expect(client.create({ - domain: 'api', - path: '/2010-04-01/Accounts/{AccountSid}/Messages.json' - })).to.be.rejectedWith('/docs/errors/20003'); + await expect( + apiClient.create({ + domain: 'api', + path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', + }), + ).to.be.rejectedWith('/docs/errors/20003'); }); - test - .it('handles bad domains', async () => { - await expect(client.create({ + test.it('handles bad domains', async () => { + await expect( + apiClient.create({ domain: 'super-rad-api', - path: '/2010-04-01/Accounts/{AccountSid}/Messages.json' - })).to.be.rejectedWith('not found'); - }); + path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', + }), + ).to.be.rejectedWith('not found'); + }); - test - .it('handles bad paths', async () => { - await expect(client.create({ + test.it('handles bad paths', async () => { + await expect( + apiClient.create({ domain: 'api', - path: '/2010-04-01/Accounts/{AccountSid}/AWESOME!!!!' - })).to.be.rejectedWith('not found'); - }); + path: '/2010-04-01/Accounts/{AccountSid}/AWESOME!!!!', + }), + ).to.be.rejectedWith('not found'); + }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { api.post(`/2010-04-01/Accounts/${accountSid}/Addresses.json`).reply(201, { - verified: 'true' + verified: 'true', }); }) .it('handles boolean parameters', async () => { - const response = await client.create({ + const response = await apiClient.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Addresses.json', - data: { EmergencyEnabled: true } + data: { EmergencyEnabled: true }, }); expect(response).to.eql({ verified: 'true' }); @@ -233,16 +254,16 @@ describe('services', () => { }); test - .nock('https://api.twilio.com', api => { + .nock('https://api.twilio.com', (api) => { api.get(`/2010-04-01/Accounts/${accountSid}/Calls/hey%23there%3F.json`).reply(200, { - status: 'in-progress' + status: 'in-progress', }); }) .it('encodes non-safe path param characters', async () => { - const response = await client.fetch({ + const response = await apiClient.fetch({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Calls/{Sid}.json', - pathParams: { Sid: 'hey#there?' } + pathParams: { Sid: 'hey#there?' }, }); expect(response).to.eql({ status: 'in-progress' }); @@ -251,14 +272,14 @@ describe('services', () => { /* eslint-disable max-nested-callbacks */ describe('getLimit', () => { test.it('gets the limit', () => { - const limit = client.getLimit({ Limit: 10 }); + const limit = apiClient.getLimit({ Limit: 10 }); expect(limit).to.equal(10); }); test.it('drops the limit if no-limit is truthy', () => { - const limit = client.getLimit({ + const limit = apiClient.getLimit({ Limit: 10, - NoLimit: true + NoLimit: true, }); expect(limit).to.be.undefined; }); @@ -266,171 +287,176 @@ describe('services', () => { test.it('adjusts the page size if greater than the limit', () => { const options = { Limit: 10, - PageSize: 20 + PageSize: 20, }; - const limit = client.getLimit(options); + const limit = apiClient.getLimit(options); expect(limit).to.equal(10); expect(options.PageSize).to.equal(10); }); test.it('does not set the page size if not provided', () => { const options = { Limit: 10 }; - const limit = client.getLimit(options); + const limit = apiClient.getLimit(options); expect(limit).to.equal(10); expect(options.PageSize).to.be.undefined; }); }); describe('regional and edge support', () => { - const defaultRegionTest = test - .nock('https://api.edge.us1.twilio.com', api => { - api.post(`/2010-04-01/Accounts/${accountSid}/Messages.json`).reply(201, { - status: 'queued' - }); - }); - - defaultRegionTest - .it('uses the default region if only edge is defined', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, edge: 'edge' } - ); + const defaultRegionTest = test.nock('https://api.edge.us1.twilio.com', (api) => { + api.post(`/2010-04-01/Accounts/${accountSid}/Messages.json`).reply(201, { + status: 'queued', + }); + }); - const response = await client.create({ - domain: 'api', - path: '/2010-04-01/Accounts/{AccountSid}/Messages.json' - }); - expect(response).to.eql({ status: 'queued' }); + defaultRegionTest.it('uses the default region if only edge is defined', async () => { + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + edge: 'edge', }); - defaultRegionTest - .it('uses the default region if edge is provided', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient } - ); + const response = await client.create({ + domain: 'api', + path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', + }); + expect(response).to.eql({ status: 'queued' }); + }); - const response = await client.create({ - domain: 'api', - path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', - edge: 'edge' - }); - expect(response).to.eql({ status: 'queued' }); + defaultRegionTest.it('uses the default region if edge is provided', async () => { + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, }); - const regionTest = test.nock('https://api.region.twilio.com', api => { + const response = await client.create({ + domain: 'api', + path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', + edge: 'edge', + }); + expect(response).to.eql({ status: 'queued' }); + }); + + const regionTest = test.nock('https://api.region.twilio.com', (api) => { api.post(`/2010-04-01/Accounts/${accountSid}/Messages.json`).reply(201, { - status: 'queued' + status: 'queued', }); }); regionTest.it('uses the client region if defined', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, region: 'region' } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + region: 'region', + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', // Should ignore the region in the uri - uri: `https://api.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); regionTest.it('uses the provided region if client region defined and region is provided', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, region: 'region2' } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + region: 'region2', + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', region: 'region', // Should ignore the region in the uri - uri: `https://api.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); regionTest.it('uses the provided region', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', region: 'region', // Should ignore the region in the uri - uri: `https://api.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); - const edgeRegionTest = - test.nock('https://api.edge.region.twilio.com', api => { - api.post(`/2010-04-01/Accounts/${accountSid}/Messages.json`).reply(201, { - status: 'queued' - }); + const edgeRegionTest = test.nock('https://api.edge.region.twilio.com', (api) => { + api.post(`/2010-04-01/Accounts/${accountSid}/Messages.json`).reply(201, { + status: 'queued', }); + }); edgeRegionTest.it('should set the region and edge properly', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, edge: 'edge', region: 'region' } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + edge: 'edge', + region: 'region', + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', // Should ignore the edge and region in the uri - uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); edgeRegionTest.it('uses the client region and provided edge when edge provided', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, edge: 'clientEdge', region: 'region' } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + edge: 'clientEdge', + region: 'region', + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', edge: 'edge', // Should ignore the edge and region in the uri - uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); edgeRegionTest.it('uses the provided region and client edge when region provided', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, edge: 'edge', region: 'clientRegion' } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + edge: 'edge', + region: 'clientRegion', + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', region: 'region', // Should ignore the edge and region in the uri - uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); edgeRegionTest.it('uses the provided region and edge', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + }); const response = await client.create({ domain: 'api', @@ -438,69 +464,70 @@ describe('services', () => { edge: 'edge', region: 'region', // Should ignore the edge and region in the uri - uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); - edgeRegionTest - .it('uses the provided region if only edge is defined and region is provided', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, edge: 'edge' } - ); + edgeRegionTest.it('uses the provided region if only edge is defined and region is provided', async () => { + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + edge: 'edge', + }); - const response = await client.create({ - domain: 'api', - path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', - region: 'region', - // Should ignore the edge and region in the uri - uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` - }); - expect(response).to.eql({ status: 'queued' }); - }); - - edgeRegionTest - .it('uses the uri region and client edge', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, edge: 'edge' } - ); + const response = await client.create({ + domain: 'api', + path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', + region: 'region', + // Should ignore the edge and region in the uri + uri: `https://api.uriEdge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, + }); + expect(response).to.eql({ status: 'queued' }); + }); - const response = await client.create({ - domain: 'api', - path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', - // Should ignore the edge in the uri - uri: `https://api.uriEdge.region.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` - }); - expect(response).to.eql({ status: 'queued' }); + edgeRegionTest.it('uses the uri region and client edge', async () => { + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + edge: 'edge', }); + const response = await client.create({ + domain: 'api', + path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', + // Should ignore the edge in the uri + uri: `https://api.uriEdge.region.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, + }); + expect(response).to.eql({ status: 'queued' }); + }); + edgeRegionTest.it('uses the client region and uri edge', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient, region: 'region' } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + region: 'region', + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', // Should ignore the region in the uri - uri: `https://api.edge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.edge.uriRegion.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); }); edgeRegionTest.it('uses the uri region and edge', async () => { - const client = new TwilioApiClient( - constants.FAKE_API_KEY, constants.FAKE_API_SECRET, - { accountSid, httpClient } - ); + const client = new TwilioApiClient(constants.FAKE_API_KEY, constants.FAKE_API_SECRET, { + accountSid, + httpClient, + }); const response = await client.create({ domain: 'api', path: '/2010-04-01/Accounts/{AccountSid}/Messages.json', - uri: `https://api.edge.region.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json` + uri: `https://api.edge.region.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`, }); expect(response).to.eql({ status: 'queued' }); });