diff --git a/docs/Upgrade-Project-From-V1.md b/docs/Upgrade-Project-From-V1.md index 07c7c132..f7879050 100644 --- a/docs/Upgrade-Project-From-V1.md +++ b/docs/Upgrade-Project-From-V1.md @@ -2,7 +2,7 @@ > **Use this guide to upgrade CLI 1.x projects for deployment with the CLI Beta (askx)** -Skills created with CLI 1.x will need to update their project structure in order to deploy with the CLI Beta (askx). To help projects upgrade to the new format, we have provided an `askx util upgrade-to-v2` command that will attempt to perform the necessary changes. +Skills created with CLI 1.x will need to update their project structure in order to deploy with the CLI Beta (askx). To help projects upgrade to the new format, we have provided an `askx util upgrade-project` command that will attempt to perform the necessary changes. ## Upgrade steps @@ -11,7 +11,7 @@ Skills created with CLI 1.x will need to update their project structure in order 2. Install ask-cli-x: `$ npm install -g ask-cli-x` 3. Upgrade your skill project with ask-cli-x. From your project's root, run: - `$ askx util upgrade-to-v2` + `$ askx util upgrade-project` * The command will make the following changes to the project structure: | | v1 project | beta (askx) project | @@ -30,7 +30,7 @@ Skills created with CLI 1.x will need to update their project structure in order **NOTE:** * No changes will be made to existing AWS resources. We will utilize existing Lambda ARN(s) from the Lambda resources list in v1. -* The upgrade-to-v2 command assumes v1 project structure, and focusing on managing existing resources in Alexa or AWS. Should this fail, we will present developers with error, link to GitHub issues to ask for support. +* The upgrade-project command assumes v1 project structure, and focusing on managing existing resources in Alexa or AWS. Should this fail, we will present developers with error, link to GitHub issues to ask for support. * The ./hooks folder is no longer needed, as ask-cli v2 can infer code build flow. If you encounter any problems during this upgrade process, please create a [issues](https://github.com/alexa-labs/ask-cli/issues) to us. diff --git a/lib/commands/util/upgrade-to-v2/helper.js b/lib/commands/util/upgrade-project/helper.js similarity index 77% rename from lib/commands/util/upgrade-to-v2/helper.js rename to lib/commands/util/upgrade-project/helper.js index 921ebce6..bceb0f80 100644 --- a/lib/commands/util/upgrade-to-v2/helper.js +++ b/lib/commands/util/upgrade-project/helper.js @@ -1,15 +1,17 @@ -const R = require('ramda'); -const path = require('path'); const fs = require('fs-extra'); +const path = require('path'); +const R = require('ramda'); + const SkillMetadataController = require('@src/controllers/skill-metadata-controller'); +const awsUtil = require('@src/clients/aws-client/aws-util'); +const CliError = require('@src/exceptions/cli-error'); const ResourcesConfig = require('@src/model/resources-config'); const Messenger = require('@src/view/messenger'); -const urlUtils = require('@src/utils/url-utils'); -const awsUtil = require('@src/clients/aws-client/aws-util'); -const hashUtils = require('@src/utils/hash-utils'); -const stringUtils = require('@src/utils/string-utils'); const CONSTANTS = require('@src/utils/constants'); +const stringUtils = require('@src/utils/string-utils'); +const hashUtils = require('@src/utils/hash-utils'); +const urlUtils = require('@src/utils/url-utils'); const ui = require('./ui'); @@ -23,10 +25,10 @@ module.exports = { }; /** - * - * @param {String} v1RootPath - * @param {String} profile - * @returns upgradeInfo { skillId, lambdaResources } + * To extract upgrade information from v1 project + * @param {String} v1RootPath the v1 file path + * @param {String} profile the profile + * @returns upgradeInfo { skillId, isHosted, lambdaResources } * upgradeInfo.lambdaResources { $alexaRegion: { arn, codeUri, v2CodeUri, runtime, handler, revisionId } } * @throws validationError */ @@ -34,23 +36,26 @@ function extractUpgradeInformation(v1RootPath, profile) { // 1.check v1 .ask/config exists const hiddenConfigPath = path.join(v1RootPath, '.ask', 'config'); if (!fs.existsSync(hiddenConfigPath)) { - throw 'Failed to find ask-cli v1 project. Please make sure this command is called at the root of the skill project.'; + throw new CliError('Failed to find ask-cli v1 project. Please make sure this command is called at the root of the skill project.'); } const hiddenConfig = fs.readJsonSync(hiddenConfigPath, 'utf-8'); const v1ProjData = { skillId: R.view(R.lensPath(['deploy_settings', profile, 'skill_id']), hiddenConfig), - isHosted: R.view(R.lensPath(['deploy_settings', profile, 'alexaHosted', 'isAlexaHostedSkill']), hiddenConfig) || false, + isHosted: R.view(R.lensPath(['alexaHosted', 'isAlexaHostedSkill']), hiddenConfig) || false, lambdaList: R.view(R.lensPath(['deploy_settings', profile, 'resources', 'lambda']), hiddenConfig) || [] }; // 2.check if skillId exists if (!stringUtils.isNonBlankString(v1ProjData.skillId)) { - throw `Failed to find skill_id for profile [${profile}]. If the skill has never been deployed in v1 ask-cli, please start from v2 structure.`; + throw new CliError(`Failed to find skill_id for profile [${profile}]. \ +If the skill has never been deployed in v1 ask-cli, please start from v2 structure.`); } - // 3.exclude hosted-skill case for now if (v1ProjData.isHosted) { - throw 'Alexa Hosted Skill is currently not supported to upgrade.'; + return { + skillId: v1ProjData.skillId, + isHosted: v1ProjData.isHosted + }; } - // 4.resolve Lambda codebase for each region + // 3.resolve Lambda codebase for each region const lambdaMapByRegion = {}; for (const lambdaResource of v1ProjData.lambdaList) { _collectLambdaMapFromResource(lambdaMapByRegion, lambdaResource); @@ -78,7 +83,7 @@ You have multiple Lambda codebases for region ${region}, we will use "${lambdaMa } } else { // set Lambda info for each alexaRegion and only re-use the Lambda ARN for the first alexaRegion (let the rest create their own Lambda) - const v2CodeUri = `.${path.sep}${CONSTANTS.FILE_PATH.SKILL_CODE.CODE}${path.sep}${stringUtils.filterNonAlphanumeric(codeUri)}`; + const v2CodeUri = `.${path.sep}${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}${path.sep}${stringUtils.filterNonAlphanumeric(codeUri)}`; lambdaMapByRegion[region] = { arn: index === 0 ? arn : undefined, codeUri, @@ -94,20 +99,25 @@ You have multiple Lambda codebases for region ${region}, we will use "${lambdaMa function _validateLambdaResource(lambdaResource) { const { alexaUsage, codeUri, runtime, handler } = lambdaResource; if (!alexaUsage || alexaUsage.length === 0) { - throw 'Please make sure your alexaUsage is not empty.'; + throw new CliError('Please make sure your alexaUsage is not empty.'); } if (!stringUtils.isNonBlankString(codeUri)) { - throw 'Please make sure your codeUri is set to the path of your Lambda code.'; + throw new CliError('Please make sure your codeUri is set to the path of your Lambda code.'); } if (!stringUtils.isNonBlankString(runtime)) { - throw `Please make sure your runtime for codeUri ${codeUri} is set.`; + throw new CliError(`Please make sure your runtime for codeUri ${codeUri} is set.`); } if (!stringUtils.isNonBlankString(handler)) { - throw `Please make sure your handler for codeUri ${codeUri} is set.`; + throw new CliError(`Please make sure your handler for codeUri ${codeUri} is set.`); } return lambdaResource; } +/** + * To confirm users with the upgrade changes + * @param {Object} upgradeInfo the upgrade info { skillId, isHosted,lambdaResources } + * @param {callback} callback { err, previewConfirm } + */ function previewUpgrade(upgradeInfo, callback) { ui.displayPreview(upgradeInfo); ui.confirmPreview((confirmErr, previewConfirm) => { @@ -115,6 +125,10 @@ function previewUpgrade(upgradeInfo, callback) { }); } +/** + * To move v1 project to legacy folder + * @param {string} v1RootPath the v1 root path + */ function moveOldProjectToLegacyFolder(v1RootPath) { const oldFiles = fs.readdirSync(v1RootPath); const legacyPath = path.join(v1RootPath, CONSTANTS.FILE_PATH.LEGACY_PATH); @@ -125,12 +139,18 @@ function moveOldProjectToLegacyFolder(v1RootPath) { }); } +/** + * To create v2 project structure + * @param {String} rootPath the root path + * @param {String} skillId the skill id + * @param {String} profile the profile + */ function createV2ProjectSkeleton(rootPath, skillId, profile) { - // prepare skill packaage folder + // prepare skill package folder const skillPackagePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE); fs.ensureDirSync(skillPackagePath); - // preprare skill code folder - const skillCodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.CODE); + // prepare skill code folder + const skillCodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA); fs.ensureDirSync(skillCodePath); // prepare ask-resources config const askResourcesJson = R.clone(ResourcesConfig.BASE); @@ -143,6 +163,15 @@ function createV2ProjectSkeleton(rootPath, skillId, profile) { fs.writeJSONSync(askResourcesFilePath, askResourcesJson, { spaces: CONSTANTS.CONFIGURATION.JSON_DISPLAY_INDENT }); } +/** + * To download skill project + * @param {String} rootPath the root path + * @param {String} skillId the skill id + * @param {String} skillStage the skill stage + * @param {String} profile the profile + * @param {Boolean} doDebug the debug flag + * @param {callback} callback { err } + */ function downloadSkillPackage(rootPath, skillId, skillStage, profile, doDebug, callback) { const skillMetaController = new SkillMetadataController({ profile, doDebug }); skillMetaController.getSkillPackage(rootPath, skillId, skillStage, (packageErr) => { @@ -161,9 +190,16 @@ function downloadSkillPackage(rootPath, skillId, skillStage, profile, doDebug, c }); } +/** + * To handle existing lambda code and update ask-resources.js + * @param {String} rootPath the root path + * @param {Object} lambdaResourcesMap the lambda code resources from old project + * lambdaResourcesMap { $alexaRegion: { arn, codeUri, handler, revisionId, runtime, v2CodeUri} } + * @param {String} profile the profile + */ function handleExistingLambdaCode(rootPath, lambdaResourcesMap, profile) { // 1.update skill infra type - ResourcesConfig.getInstance().setSkillInfraType(profile, '@ask-cli/lambda-deployer'); + ResourcesConfig.getInstance().setSkillInfraType(profile, CONSTANTS.DEPLOYER_TYPE.LAMBDA.NAME); // 2.set userConfig from default region Lambda configuration // default will always exist as it's required in a set of valid Lambda resources from the v1 project let defaultRuntime, defaultHandler; diff --git a/lib/commands/util/upgrade-project/hosted-skill-helper.js b/lib/commands/util/upgrade-project/hosted-skill-helper.js new file mode 100644 index 00000000..081f3f48 --- /dev/null +++ b/lib/commands/util/upgrade-project/hosted-skill-helper.js @@ -0,0 +1,72 @@ +const fs = require('fs-extra'); +const R = require('ramda'); +const path = require('path'); + +const SkillMetadataController = require('@src/controllers/skill-metadata-controller'); +const ResourcesConfig = require('@src/model/resources-config'); +const CONSTANTS = require('@src/utils/constants'); + +module.exports = { + createV2ProjectSkeleton, + downloadSkillPackage, + handleExistingLambdaCode +}; + +/** + * To create v2 project structure for an Alexa hosted skill project + * @param {String} rootPath the root path + * @param {String} skillId the skill id + * @param {String} profile the profile + */ +function createV2ProjectSkeleton(rootPath, skillId, profile) { + // prepare skill package folder + const skillPackagePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE); + fs.ensureDirSync(skillPackagePath); + // prepare skill code folder + const skillCodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA); + fs.ensureDirSync(skillCodePath); + // prepare ask-resources config + const askResourcesJson = R.clone(ResourcesConfig.BASE); + askResourcesJson.profiles[profile] = { + skillId + }; + const askResourcesFilePath = path.join(rootPath, CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG); + fs.writeJSONSync(askResourcesFilePath, askResourcesJson, { spaces: CONSTANTS.CONFIGURATION.JSON_DISPLAY_INDENT }); +} + +/** + * To download skill project for an Alexa hosted skill project + * @param {String} rootPath the root path + * @param {String} skillId the skill id + * @param {String} skillStage the skill stage + * @param {String} profile the profile + * @param {Boolean} doDebug the debug flag + * @param {callback} callback { err } + */ +function downloadSkillPackage(rootPath, skillId, skillStage, profile, doDebug, callback) { + const skillMetaController = new SkillMetadataController({ profile, doDebug }); + skillMetaController.getSkillPackage(rootPath, skillId, skillStage, (packageErr) => { + if (packageErr) { + return callback(packageErr); + } + callback(); + }); +} + +/** + * To handle existing lambda code and update ask-resources.js for an Alexa hosted skill project + * @param {String} rootPath the root path + * @param {Object} lambdaResourcesMap the lambda code resources from old project { profile: { arn, codeUri, handler, revisionId, runtime, v2CodeUri} } + * @param {String} profile the profile + */ +function handleExistingLambdaCode(rootPath, profile) { + // 1.update skill infra type + ResourcesConfig.getInstance().setSkillInfraType(profile, CONSTANTS.DEPLOYER_TYPE.HOSTED.NAME); + // 2.copy Lambda code from legacy folder and set deployState for each region + const legacyFolderPath = path.join(rootPath, CONSTANTS.FILE_PATH.LEGACY_PATH); + // 3.1 copy code from v1 project to v2 + const v1CodePath = path.join(legacyFolderPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA); + const v2CodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA); + fs.copySync(v1CodePath, v2CodePath); + ResourcesConfig.getInstance().write(); +} diff --git a/lib/commands/util/upgrade-project/index.js b/lib/commands/util/upgrade-project/index.js new file mode 100644 index 00000000..91e50c79 --- /dev/null +++ b/lib/commands/util/upgrade-project/index.js @@ -0,0 +1,130 @@ +const path = require('path'); + +const { AbstractCommand } = require('@src/commands/abstract-command'); +const optionModel = require('@src/commands/option-model'); +const ResourcesConfig = require('@src/model/resources-config'); +const Messenger = require('@src/view/messenger'); +const profileHelper = require('@src/utils/profile-helper'); +const CONSTANTS = require('@src/utils/constants'); + +const helper = require('./helper'); +const hostedSkillHelper = require('./hosted-skill-helper'); + +class UpgradeProjectCommand extends AbstractCommand { + name() { + return 'upgrade-project'; + } + + description() { + return 'upgrade the v1 ask-cli skill project to v2 structure'; + } + + requiredOptions() { + return []; + } + + optionalOptions() { + return ['profile', 'debug']; + } + + handle(cmd, cb) { + // 1.confirm project is upgrade-able + let profile, upgradeInfo; + try { + profile = profileHelper.runtimeProfile(cmd.profile); + upgradeInfo = helper.extractUpgradeInformation(process.cwd(), profile); + } catch (checkErr) { + Messenger.getInstance().error(checkErr); + return cb(checkErr); + } + // 2.preview new project structure + helper.previewUpgrade(upgradeInfo, (previewErr, previewConfirm) => { + if (previewErr) { + Messenger.getInstance().error(previewErr); + return cb(previewErr); + } + if (!previewConfirm) { + Messenger.getInstance().info('Command upgrade-project aborted.'); + return cb(); + } + // 3.create v2 project based on the upgrade info + if (upgradeInfo.isHosted) { + _createV2HostedSkillProject(upgradeInfo, profile, cmd.debug, (v2Err) => { + if (v2Err) { + Messenger.getInstance().error(v2Err); + return cb(v2Err); + } + Messenger.getInstance().info('Project migration finished.'); + cb(); + }); + } else { + _createV2NonHostedSkillProject(upgradeInfo, profile, cmd.debug, (v2Err) => { + if (v2Err) { + Messenger.getInstance().error(v2Err); + return cb(v2Err); + } + Messenger.getInstance().info('Project migration finished.'); + cb(); + }); + } + }); + } +} + +function _createV2HostedSkillProject(upgradeInfo, profile, doDebug, callback) { + const rootPath = process.cwd(); + const { skillId } = upgradeInfo; + try { + // 1.move v1 skill project content into legacy folder + helper.moveOldProjectToLegacyFolder(rootPath); + // 2.instantiate MVC and ask-resource config + hostedSkillHelper.createV2ProjectSkeleton(rootPath, skillId, profile); + new ResourcesConfig(path.join(rootPath, CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG)); + } catch (initProjErr) { + return callback(initProjErr); + } + // 3.import skill metadata from skillId + hostedSkillHelper.downloadSkillPackage(rootPath, skillId, CONSTANTS.SKILL.STAGE.DEVELOPMENT, profile, doDebug, (packageErr) => { + if (packageErr) { + return callback(packageErr); + } + // 4.copy Lambda code to skill code and delete old project + try { + hostedSkillHelper.handleExistingLambdaCode(rootPath, profile); + callback(); + } catch (codeErr) { + callback(codeErr); + } + }); +} + +function _createV2NonHostedSkillProject(upgradeInfo, profile, doDebug, callback) { + const rootPath = process.cwd(); + const { skillId, lambdaResources } = upgradeInfo; + try { + // 1.move v1 skill project content into legacy folder + helper.moveOldProjectToLegacyFolder(rootPath); + // 2.instantiate MVC and ask-resource config + helper.createV2ProjectSkeleton(rootPath, skillId, profile); + new ResourcesConfig(path.join(rootPath, CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG)); + } catch (initProjErr) { + return callback(initProjErr); + } + + // 3.import skill metadata from skillId + helper.downloadSkillPackage(rootPath, skillId, CONSTANTS.SKILL.STAGE.DEVELOPMENT, profile, doDebug, (packageErr) => { + if (packageErr) { + return callback(packageErr); + } + // 4.copy Lambda code to skill code + try { + helper.handleExistingLambdaCode(rootPath, lambdaResources, profile); + callback(); + } catch (codeErr) { + callback(codeErr); + } + }); +} + +module.exports = UpgradeProjectCommand; +module.exports.createCommand = new UpgradeProjectCommand(optionModel).createCommand(); diff --git a/lib/commands/util/upgrade-to-v2/ui.js b/lib/commands/util/upgrade-project/ui.js similarity index 69% rename from lib/commands/util/upgrade-to-v2/ui.js rename to lib/commands/util/upgrade-project/ui.js index e8f874df..af27cb32 100644 --- a/lib/commands/util/upgrade-to-v2/ui.js +++ b/lib/commands/util/upgrade-project/ui.js @@ -1,6 +1,7 @@ -const R = require('ramda'); const path = require('path'); +const R = require('ramda'); const inquirer = require('inquirer'); + const Messenger = require('@src/view/messenger'); const CONSTANTS = require('@src/utils/constants'); @@ -9,14 +10,23 @@ module.exports = { confirmPreview }; +/** + * To display the update information before migration + * @param {Object} upgradeInfo upgradeInfo { skillId, isHosted,lambdaResources } + * upgradeInfo.lambdaResources { $alexaRegion: { arn, codeUri, v2CodeUri, runtime, handler, revisionId } } + */ function displayPreview(upgradeInfo) { - const { skillId, lambdaResources } = upgradeInfo; + const { skillId, isHosted, lambdaResources } = upgradeInfo; Messenger.getInstance().info(`Preview of the upgrade result from v1 to v2: - The original v1 skill project will be entirely moved to .${path.sep}${CONSTANTS.FILE_PATH.LEGACY_PATH}${path.sep} - JSON files for Skill ID ${skillId} (such as skill.json) will be moved to .${path.sep}${CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE}${path.sep} -${_displayLambdaCodePreview(lambdaResources)}`); +${_displayLambdaCodePreview(isHosted, lambdaResources)}`); } +/** + * To confirm user with the migration + * @param {callback} callback { err, confirmPreview } + */ function confirmPreview(callback) { const question = { type: 'confirm', @@ -30,11 +40,14 @@ function confirmPreview(callback) { }); } -function _displayLambdaCodePreview(lambdaResources) { +function _displayLambdaCodePreview(isHosted, lambdaResources) { + let lambdaPreview = `- Existing Lambda codebase will be moved into "${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}" folder`; + if (isHosted) { + return lambdaPreview; + } if (R.keys(lambdaResources).length === 0) { return '- No existing Lambda in the v1 "lambda" resource thus no action for Lambda codebase.'; } - let lambdaPreview = '- Existing Lambda codebase will be moved into "code" folder'; R.keys(lambdaResources).forEach((region) => { const { codeUri, v2CodeUri, arn } = lambdaResources[region]; // TODO check when codeUri is a single file diff --git a/lib/commands/util/upgrade-to-v2/index.js b/lib/commands/util/upgrade-to-v2/index.js deleted file mode 100644 index 8c4507c5..00000000 --- a/lib/commands/util/upgrade-to-v2/index.js +++ /dev/null @@ -1,89 +0,0 @@ -const path = require('path'); -const { AbstractCommand } = require('@src/commands/abstract-command'); -const optionModel = require('@src/commands/option-model'); -const ResourcesConfig = require('@src/model/resources-config'); -const Messenger = require('@src/view/messenger'); -const profileHelper = require('@src/utils/profile-helper'); -const CONSTANTS = require('@src/utils/constants'); - -const helper = require('./helper'); - -class UpgradeToV2Command extends AbstractCommand { - name() { - return 'upgrade-to-v2'; - } - - description() { - return 'upgrade the v1 ask-cli skill project to v2 structure'; - } - - requiredOptions() { - return []; - } - - optionalOptions() { - return ['profile', 'debug']; - } - - handle(cmd, cb) { - // 1.confirm project is upgrade-able - let profile, upgradeInfo; - try { - profile = profileHelper.runtimeProfile(cmd.profile); - upgradeInfo = helper.extractUpgradeInformation(process.cwd(), profile); - } catch (checkErr) { - Messenger.getInstance().error(checkErr); - return cb(checkErr); - } - // 2.preview new project strcuture - helper.previewUpgrade(upgradeInfo, (previewErr, previewConfirm) => { - if (previewErr) { - Messenger.getInstance().error(previewErr); - return cb(previewErr); - } - if (!previewConfirm) { - Messenger.getInstance().info('Command upgrade-to-v2 aborted.'); - return cb(); - } - // 3.create v2 project based on the upgrade info - createV2Project(upgradeInfo, profile, cmd.debug, (v2Err) => { - if (v2Err) { - Messenger.getInstance().error(v2Err); - return cb(v2Err); - } - cb(); - }); - }); - } -} - -function createV2Project(upgradeInfo, profile, doDebug, callback) { - const rootPath = process.cwd(); - const { skillId, lambdaResources } = upgradeInfo; - try { - // 1.move v1 skill project content into legacy folder - helper.moveOldProjectToLegacyFolder(rootPath); - // 2.instantiate MVC and ask-resource config - helper.createV2ProjectSkeleton(rootPath, skillId, profile); - new ResourcesConfig(path.join(rootPath, CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG)); - } catch (initProjErr) { - return callback(initProjErr); - } - - // 3.import skill metadata from skillId - helper.downloadSkillPackage(rootPath, skillId, CONSTANTS.SKILL.STAGE.DEVELOPMENT, profile, doDebug, (packageErr) => { - if (packageErr) { - return callback(packageErr); - } - // 4.copy Lambda code to skill code - try { - helper.handleExistingLambdaCode(rootPath, lambdaResources, profile); - } catch (codeErr) { - callback(codeErr); - } - callback(); - }); -} - -module.exports = UpgradeToV2Command; -module.exports.createCommand = new UpgradeToV2Command(optionModel).createCommand(); diff --git a/lib/commands/util/util-commander.js b/lib/commands/util/util-commander.js index 0b9c091a..46a60df8 100644 --- a/lib/commands/util/util-commander.js +++ b/lib/commands/util/util-commander.js @@ -1,7 +1,7 @@ const commander = require('commander'); const UTIL_COMMAND_MAP = { - 'upgrade-to-v2': '@src/commands/util/upgrade-to-v2', + 'upgrade-project': '@src/commands/util/upgrade-project', 'git-credentials-helper': '@src/commands/util/git-credentials-helper' }; diff --git a/lib/utils/constants.js b/lib/utils/constants.js index e548a503..6922265a 100644 --- a/lib/utils/constants.js +++ b/lib/utils/constants.js @@ -167,7 +167,8 @@ module.exports.FILE_PATH = { MANIFEST: 'skill.json' }, SKILL_CODE: { - CODE: 'code' + CODE: 'code', // TODO ; replaced with LAMBDA + LAMBDA: 'lambda' }, SKILL_INFRASTRUCTURE: { INFRASTRUCTURE: 'infrastructure' diff --git a/test/unit/commands/util/upgrade-project/helper-test.js b/test/unit/commands/util/upgrade-project/helper-test.js new file mode 100644 index 00000000..740c0dbc --- /dev/null +++ b/test/unit/commands/util/upgrade-project/helper-test.js @@ -0,0 +1,464 @@ +const { expect } = require('chai'); +const R = require('ramda'); +const sinon = require('sinon'); +const fs = require('fs-extra'); +const path = require('path'); + +const awsUtil = require('@src/clients/aws-client/aws-util'); +const helper = require('@src/commands/util/upgrade-project/helper'); +const ui = require('@src/commands/util/upgrade-project/ui'); +const SkillMetadataController = require('@src/controllers/skill-metadata-controller'); +const CLiError = require('@src/exceptions/cli-error'); +const ResourcesConfig = require('@src/model/resources-config'); +const Messenger = require('@src/view/messenger'); +const CONSTANTS = require('@src/utils/constants'); +const hashUtils = require('@src/utils/hash-utils'); + +describe('Commands upgrade-project test - helper test', () => { + const TEST_ERROR = 'testError'; + const TEST_PROFILE = 'default'; + const TEST_REGION = 'default'; + const TEST_REGION_NA = 'NA'; + const TEST_AWS_REGION = 'us-west-2'; + const TEST_DO_DEBUG = false; + const TEST_SKILL_ID = 'skillId'; + const TEST_SKILL_STAGE = 'development'; + const TEST_CODE_URI = 'codeUri'; + const TEST_V2_CODE_URI = `${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}/${TEST_CODE_URI}`; + const TEST_RUNTIME = 'runtime'; + const TEST_HANDLER = 'handler'; + const TEST_REVISION_ID = 'revisionId'; + const TEST_ROOT_PATH = 'rootPath'; + const TEST_ARN = 'arn:aws:lambda:us-west-2:123456789012:function:ask-custom-skill-sample-nodejs-fact-default'; + const FIXTURE_RESOURCES_CONFIG_FILE_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'upgrade-resources-config.json'); + const TEST_LAMBDAS = [ + { + alexaUsage: ['custom/default'], + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + revisionId: TEST_REVISION_ID + } + ]; + const TEST_RESOURCE_MAP = { + [TEST_REGION]: { + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + v2CodeUri: `./${TEST_V2_CODE_URI}`, + revisionId: TEST_REVISION_ID + }, + [TEST_REGION_NA]: { + arn: undefined, + codeUri: TEST_CODE_URI, + runtime: undefined, + handler: undefined, + v2CodeUri: `./${TEST_V2_CODE_URI}`, + revisionId: undefined + } + }; + + describe('# test helper method - extractUpgradeInformation', () => { + const TEST_V1_CONFIG_PATH = 'v1ConfigPath'; + const formV1Config = (skillId, isHosted, lambdaResources) => { + const result = { deploy_settings: {}, alexaHosted: {} }; + result.deploy_settings[TEST_PROFILE] = { + skill_id: skillId, + resources: { + lambda: lambdaResources + } + }; + result.alexaHosted = { + isAlexaHostedSkill: isHosted + }; + return result; + }; + + beforeEach(() => { + sinon.stub(path, 'join').returns(TEST_V1_CONFIG_PATH); + sinon.stub(fs, 'existsSync').returns(true); + sinon.stub(fs, 'readJsonSync').returns({}); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('| v1 project file does not exist, expect throw error', () => { + // setup + fs.existsSync.returns(false); + // call & verify + expect(() => helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE)).throw(CLiError, + 'Failed to find ask-cli v1 project. Please make sure this command is called at the root of the skill project.'); + }); + + it('| skill ID does not exist, expect throw error message', () => { + // setup + fs.readJsonSync.returns(formV1Config(' ', false, {})); + // call & verify + expect(() => helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE)).throw(CLiError, + `Failed to find skill_id for profile [${TEST_PROFILE}]. \ +If the skill has never been deployed in v1 ask-cli, please start from v2 structure.`); + }); + + it('| skill project is alexa hosted skill, expect result correctly', () => { + // setup + fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, true, null)); + // call + const info = helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); + // verify + expect(info).deep.equal({ + skillId: TEST_SKILL_ID, + isHosted: true + }); + }); + + describe('test helper method - extractUpgradeInformation : _validateLambdaResource', () => { + [ + { + testCase: '| validate lambda resources fails at alexaUsage, expect throw error message', + lambdas: [ + { alexaUsage: [] } + ], + expectError: 'Please make sure your alexaUsage is not empty.' + }, + { + testCase: '| validate lambda resources fails at codeUri, expect throw error message', + lambdas: [ + { + alexaUsage: ['custom/default'], + codeUri: ' ' + } + ], + expectError: 'Please make sure your codeUri is set to the path of your Lambda code.' + }, + { + testCase: '| validate lambda resources fails at runtime, expect throw error message', + lambdas: [ + { + alexaUsage: ['custom/default'], + codeUri: TEST_CODE_URI, + runtime: ' ' + } + ], + expectError: `Please make sure your runtime for codeUri ${TEST_CODE_URI} is set.` + }, + { + testCase: '| validate lambda resources fails at handler, expect throw error message', + lambdas: [ + { + alexaUsage: ['custom/default'], + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: '' + } + ], + expectError: `Please make sure your handler for codeUri ${TEST_CODE_URI} is set.` + } + ].forEach(({ testCase, lambdas, expectError }) => { + it(testCase, () => { + // setup + fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, false, lambdas)); + // call & verify + expect(() => helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE)).throw(CLiError, expectError); + }); + }); + }); + + describe('test helper method - extractUpgradeInformation : _collectLambdaMapFromResource', () => { + let warnStub; + + beforeEach(() => { + warnStub = sinon.stub(); + sinon.stub(Messenger, 'getInstance').returns({ + warn: warnStub + }); + }); + + it('| when no Lambda ARN exists, skip the upgrade with a warning message', () => { + // setup + fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, false, TEST_LAMBDAS)); + // call + const info = helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); + // verify + expect(warnStub.args[0][0]).equal('Skip Lambda resource with alexaUsage "custom/default" since this Lambda is not deployed.'); + expect(info).deep.equal({ + skillId: TEST_SKILL_ID, + lambdaResources: {} + }); + }); + + it('| Lambda ARN exists, multiple codebase for a single region, expect result return', () => { + // setup + const TEST_LAMBDAS_MULTIPLE_CODEBASE = [ + { + alexaUsage: ['custom/default', 'custom/NA'], + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + revisionId: TEST_REVISION_ID + }, + { + alexaUsage: ['custom/default'], + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + revisionId: TEST_REVISION_ID + }, + { + alexaUsage: ['custom/default'], + arn: TEST_ARN, + codeUri: 'codeUri1', + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + revisionId: TEST_REVISION_ID + } + ]; + fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, false, TEST_LAMBDAS_MULTIPLE_CODEBASE)); + // call + const info = helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); + // verify + expect(warnStub.args[0][0]).equal(`Currently ask-cli requires one Lambda codebase per region. \ +You have multiple Lambda codebases for region ${TEST_REGION}, we will use "${TEST_CODE_URI}" as the codebase for this region.`); + expect(info).deep.equal({ + skillId: TEST_SKILL_ID, + lambdaResources: TEST_RESOURCE_MAP + }); + }); + }); + }); + + describe('# test helper method - previewUpgrade', () => { + const TEST_UPGRADE_INFO = {}; + afterEach(() => { + sinon.restore(); + }); + + it('| user not confirm upgrade, expect result return', (done) => { + // setup + sinon.stub(ui, 'displayPreview'); + sinon.stub(ui, 'confirmPreview').callsArgWith(0, TEST_ERROR); + // call + helper.previewUpgrade(TEST_UPGRADE_INFO, (err, res) => { + // verify + expect(err).equal(TEST_ERROR); + expect(res).equal(null); + done(); + }); + }); + + it('| user confirm upgrade, expect result return', (done) => { + // setup + sinon.stub(ui, 'displayPreview'); + sinon.stub(ui, 'confirmPreview').callsArgWith(0, null, true); + // call + helper.previewUpgrade(TEST_UPGRADE_INFO, (err, res) => { + // verify + expect(err).equal(null); + expect(res).equal(true); + done(); + }); + }); + }); + + describe('# test helper method - moveOldProjectToLegacyFolder', () => { + const TEST_FILE = 'file'; + const TEST_OLD_FILES = [TEST_FILE]; + + afterEach(() => { + sinon.restore(); + }); + + it('| move old project to legacy folder, expect files\' path correctly', () => { + // setup + sinon.stub(fs, 'readdirSync').returns(TEST_OLD_FILES); + sinon.stub(fs, 'ensureDirSync'); + const moveStub = sinon.stub(fs, 'moveSync'); + // call + helper.moveOldProjectToLegacyFolder(TEST_ROOT_PATH); + // verify + expect(moveStub.args[0][0]).eq(TEST_FILE); + expect(moveStub.args[0][1]).eq(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.LEGACY_PATH}/${TEST_FILE}`); + }); + }); + + describe('# test helper method - createV2ProjectSkeleton', () => { + afterEach(() => { + sinon.restore(); + }); + + it('| crate v2 project skeleton, expect write JSON file correctly', () => { + // setup + const ensureDirStub = sinon.stub(fs, 'ensureDirSync'); + const writeStub = sinon.stub(fs, 'writeJSONSync'); + sinon.stub(R, 'clone').returns({ profiles: {} }); + // call + helper.createV2ProjectSkeleton(TEST_ROOT_PATH, TEST_SKILL_ID, TEST_PROFILE); + expect(ensureDirStub.args[0][0]).eq(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE}`); + expect(ensureDirStub.args[1][0]).eq(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}`); + expect(writeStub.args[0][0]).eq(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG}`); + expect(writeStub.args[0][1]).deep.equal({ + profiles: { + [TEST_PROFILE]: { + skillId: TEST_SKILL_ID, + skillMetadata: {}, + code: {} + } + } + }); + }); + }); + + describe('# test helper method - downloadSkillPackage', () => { + beforeEach(() => { + new ResourcesConfig(FIXTURE_RESOURCES_CONFIG_FILE_PATH); + }); + + afterEach(() => { + sinon.restore(); + ResourcesConfig.dispose(); + }); + + it('| skillMetaController getSkillPackage fails, expect callback error', (done) => { + // setup + sinon.stub(SkillMetadataController.prototype, 'getSkillPackage').callsArgWith(3, TEST_ERROR); + // call + helper.downloadSkillPackage(TEST_ROOT_PATH, TEST_SKILL_ID, TEST_SKILL_STAGE, TEST_PROFILE, TEST_DO_DEBUG, (err) => { + expect(err).equal(TEST_ERROR); + done(); + }); + }); + + it('| skillMetaController getSkillPackage passes, hashUtils fails, expect error return', (done) => { + // setup + sinon.stub(SkillMetadataController.prototype, 'getSkillPackage').callsArgWith(3, null); + sinon.stub(hashUtils, 'getHash').callsArgWith(1, TEST_ERROR); + // call + helper.downloadSkillPackage(TEST_ROOT_PATH, TEST_SKILL_ID, TEST_SKILL_STAGE, TEST_PROFILE, TEST_DO_DEBUG, (err) => { + // verify + expect(err).equal(TEST_ERROR); + done(); + }); + }); + + it('| hashUtils passes, expect no error return', (done) => { + // setup + const TEST_HASH = 'hash'; + sinon.stub(SkillMetadataController.prototype, 'getSkillPackage').callsArgWith(3, null); + sinon.stub(hashUtils, 'getHash').callsArgWith(1, null, TEST_HASH); + // call + helper.downloadSkillPackage(TEST_ROOT_PATH, TEST_SKILL_ID, TEST_SKILL_STAGE, TEST_PROFILE, TEST_DO_DEBUG, (err) => { + // verify + expect(err).eq(undefined); + expect(ResourcesConfig.getInstance().getSkillMetaSrc(TEST_PROFILE)).equal('./skill-package'); + expect(ResourcesConfig.getInstance().getSkillMetaLastDeployHash(TEST_PROFILE)).equal(TEST_HASH); + done(); + }); + }); + }); + + describe('# test helper method - handleExistingLambdaCode', () => { + beforeEach(() => { + new ResourcesConfig(FIXTURE_RESOURCES_CONFIG_FILE_PATH); + }); + + afterEach(() => { + sinon.restore(); + ResourcesConfig.dispose(); + }); + + it('| handle lambdaCode and update resources JSON file, expect update correctly', () => { + // setup + const TEST_CONFIG = { + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + awsRegion: TEST_AWS_REGION, + regionalOverrides: { + NA: { + runtime: undefined, + handler: undefined + } + } + }; + sinon.stub(awsUtil, 'getAWSProfile'); + sinon.stub(awsUtil, 'getCLICompatibleDefaultRegion').returns(TEST_AWS_REGION); + const copyStub = sinon.stub(fs, 'copySync'); + // call + helper.handleExistingLambdaCode(TEST_ROOT_PATH, TEST_RESOURCE_MAP, TEST_PROFILE); + // verify + expect(ResourcesConfig.getInstance().getSkillInfraType(TEST_PROFILE)).deep.equal(CONSTANTS.DEPLOYER_TYPE.LAMBDA.NAME); + expect(ResourcesConfig.getInstance().getSkillInfraUserConfig(TEST_PROFILE)).deep.equal(TEST_CONFIG); + expect(copyStub.args[0][0]).equal(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.LEGACY_PATH}/${TEST_CODE_URI}`); + expect(copyStub.args[0][1]).equal(`${TEST_ROOT_PATH}/${TEST_V2_CODE_URI}`); + expect(ResourcesConfig.getInstance().getSkillInfraDeployState(TEST_PROFILE)[TEST_PROFILE].lambda.arn).deep.equal(TEST_ARN); + }); + + it('| handle lambdaCode and update resources JSON file with overwrite, expect, expect update correctly', () => { + // setup + const TEST_RESOURCE_MAP_MULTIPLE_REGION = { + [TEST_REGION]: { + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + v2CodeUri: TEST_V2_CODE_URI, + revisionId: TEST_REVISION_ID + }, + [TEST_REGION_NA]: { + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + v2CodeUri: TEST_V2_CODE_URI, + revisionId: TEST_REVISION_ID + }, + FE: { + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: 'JAVA', + handler: 'index.js', + v2CodeUri: TEST_V2_CODE_URI, + revisionId: TEST_REVISION_ID + }, + IN: { + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: 'JAVA', + handler: 'index.js', + v2CodeUri: TEST_V2_CODE_URI, + revisionId: TEST_REVISION_ID + } + }; + // setup + const TEST_CONFIG = { + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + awsRegion: TEST_AWS_REGION, + regionalOverrides: { + FE: { + handler: 'index.js', + runtime: 'JAVA' + }, + IN: { + handler: 'index.js', + runtime: 'JAVA' + } + } + }; + sinon.stub(awsUtil, 'getAWSProfile'); + sinon.stub(awsUtil, 'getCLICompatibleDefaultRegion').returns(TEST_AWS_REGION); + const copyStub = sinon.stub(fs, 'copySync'); + // call + helper.handleExistingLambdaCode(TEST_ROOT_PATH, TEST_RESOURCE_MAP_MULTIPLE_REGION, TEST_PROFILE); + // verify + expect(ResourcesConfig.getInstance().getSkillInfraType(TEST_PROFILE)).deep.equal(CONSTANTS.DEPLOYER_TYPE.LAMBDA.NAME); + expect(copyStub.args[0][0]).equal(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.LEGACY_PATH}/${TEST_CODE_URI}`); + expect(copyStub.args[0][1]).equal(`${TEST_ROOT_PATH}/${TEST_V2_CODE_URI}`); + expect(ResourcesConfig.getInstance().getSkillInfraDeployState(TEST_PROFILE)[TEST_PROFILE].lambda.arn).deep.equal(TEST_ARN); + expect(ResourcesConfig.getInstance().getSkillInfraUserConfig(TEST_PROFILE)).deep.equal(TEST_CONFIG); + }); + }); +}); diff --git a/test/unit/commands/util/upgrade-project/hosted-skill-helper-test.js b/test/unit/commands/util/upgrade-project/hosted-skill-helper-test.js new file mode 100644 index 00000000..850999b8 --- /dev/null +++ b/test/unit/commands/util/upgrade-project/hosted-skill-helper-test.js @@ -0,0 +1,103 @@ +const { expect } = require('chai'); +const fs = require('fs-extra'); +const path = require('path'); +const R = require('ramda'); +const sinon = require('sinon'); + +const awsUtil = require('@src/clients/aws-client/aws-util'); +const hostedSkillHelper = require('@src/commands/util/upgrade-project/hosted-skill-helper'); +const SkillMetadataController = require('@src/controllers/skill-metadata-controller'); +const ResourcesConfig = require('@src/model/resources-config'); +const CONSTANTS = require('@src/utils/constants'); + +describe('Commands upgrade-project test - hosted skill helper test', () => { + const TEST_ERROR = 'testError'; + const TEST_PROFILE = 'default'; + const TEST_AWS_REGION = 'us-west-2'; + const TEST_DO_DEBUG = false; + const TEST_SKILL_ID = 'skillId'; + const TEST_SKILL_STAGE = 'development'; + const TEST_ROOT_PATH = 'rootPath'; + const FIXTURE_RESOURCES_CONFIG_FILE_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'hosted-skill-resources-config.json'); + + describe('# test helper method - createV2ProjectSkeleton', () => { + afterEach(() => { + sinon.restore(); + }); + + it('| crate v2 project skeleton, expect write JSON file correctly', () => { + // setup + const ensureDirStub = sinon.stub(fs, 'ensureDirSync'); + const writeStub = sinon.stub(fs, 'writeJSONSync'); + sinon.stub(R, 'clone').returns({ profiles: {} }); + // call + hostedSkillHelper.createV2ProjectSkeleton(TEST_ROOT_PATH, TEST_SKILL_ID, TEST_PROFILE); + expect(ensureDirStub.args[0][0]).eq(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE}`); + expect(ensureDirStub.args[1][0]).eq(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}`); + expect(writeStub.args[0][0]).eq(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG}`); + expect(writeStub.args[0][1]).deep.equal({ + profiles: { + [TEST_PROFILE]: { + skillId: TEST_SKILL_ID + } + } + }); + }); + }); + + describe('# test helper method - downloadSkillPackage', () => { + beforeEach(() => { + new ResourcesConfig(FIXTURE_RESOURCES_CONFIG_FILE_PATH); + }); + + afterEach(() => { + sinon.restore(); + ResourcesConfig.dispose(); + }); + + it('| skillMetaController getSkillPackage fails, expect callback error', (done) => { + // setup + sinon.stub(SkillMetadataController.prototype, 'getSkillPackage').callsArgWith(3, TEST_ERROR); + // call + hostedSkillHelper.downloadSkillPackage(TEST_ROOT_PATH, TEST_SKILL_ID, TEST_SKILL_STAGE, TEST_PROFILE, TEST_DO_DEBUG, (err) => { + expect(err).equal(TEST_ERROR); + done(); + }); + }); + + it('| skillMetaController getSkillPackage passes, hashUtils fails, expect no error return', (done) => { + // setup + sinon.stub(SkillMetadataController.prototype, 'getSkillPackage').callsArgWith(3, null); + // call + hostedSkillHelper.downloadSkillPackage(TEST_ROOT_PATH, TEST_SKILL_ID, TEST_SKILL_STAGE, TEST_PROFILE, TEST_DO_DEBUG, (err) => { + // verify + expect(err).equal(undefined); + done(); + }); + }); + }); + + describe('# test helper method - handleExistingLambdaCode', () => { + beforeEach(() => { + new ResourcesConfig(FIXTURE_RESOURCES_CONFIG_FILE_PATH); + }); + + afterEach(() => { + sinon.restore(); + ResourcesConfig.dispose(); + }); + + it('| handle lambdaCode and update resources JSON file, expect update correctly', () => { + // setup + sinon.stub(awsUtil, 'getAWSProfile'); + sinon.stub(awsUtil, 'getCLICompatibleDefaultRegion').returns(TEST_AWS_REGION); + const copyStub = sinon.stub(fs, 'copySync'); + // call + hostedSkillHelper.handleExistingLambdaCode(TEST_ROOT_PATH, TEST_PROFILE); + // verify + // expect(ResourcesConfig.getInstance().getSkillInfraType(TEST_PROFILE)).deep.equal(CONSTANTS.DEPLOYER_TYPE.HOSTED.NAME); + expect(copyStub.args[0][0]).equal(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.LEGACY_PATH}/${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}`); + expect(copyStub.args[0][1]).equal(`${TEST_ROOT_PATH}/${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}`); + }); + }); +}); diff --git a/test/unit/commands/util/upgrade-project/index-test.js b/test/unit/commands/util/upgrade-project/index-test.js new file mode 100644 index 00000000..cf4c5537 --- /dev/null +++ b/test/unit/commands/util/upgrade-project/index-test.js @@ -0,0 +1,367 @@ +const { expect } = require('chai'); +const path = require('path'); +const sinon = require('sinon'); + +const UpgradeProjectCommand = require('@src/commands/util/upgrade-project/index'); +const helper = require('@src/commands/util/upgrade-project/helper'); +const hostedSkillHelper = require('@src/commands/util/upgrade-project/hosted-skill-helper'); +const optionModel = require('@src/commands/option-model'); +const CLiError = require('@src/exceptions/cli-error'); +const Messenger = require('@src/view/messenger'); +const profileHelper = require('@src/utils/profile-helper'); + +describe('Commands init test - command class test', () => { + const TEST_PROFILE = 'default'; + const TEST_ERROR = 'upgrade project error'; + const TEST_SKILL_ID = 'skillId'; + const FIXTURE_HOSTED_SKILL_RESOURCES_CONFIG = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'hosted-skill-resources-config.json'); + const FIXTURE_RESOURCES_CONFIG = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'upgrade-resources-config.json'); + let infoStub; + let errorStub; + let warnStub; + + beforeEach(() => { + infoStub = sinon.stub(); + errorStub = sinon.stub(); + warnStub = sinon.stub(); + sinon.stub(Messenger, 'getInstance').returns({ + info: infoStub, + error: errorStub, + warn: warnStub + }); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('| validate command information is set correctly', () => { + const instance = new UpgradeProjectCommand(optionModel); + expect(instance.name()).equal('upgrade-project'); + expect(instance.description()).equal('upgrade the v1 ask-cli skill project to v2 structure'); + expect(instance.requiredOptions()).deep.equal([]); + expect(instance.optionalOptions()).deep.equal(['profile', 'debug']); + }); + + describe('validate command handle', () => { + const TEST_CMD = { + profile: TEST_PROFILE + }; + + describe('command handle - pre upgrade check', () => { + let instance; + + beforeEach(() => { + instance = new UpgradeProjectCommand(optionModel); + sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('| when profile is not correct, expect throw error', (done) => { + // setup + profileHelper.runtimeProfile.throws(new Error('error')); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal('error'); + expect(errorStub.args[0][0].message).equal('error'); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| when extract upgrade information fails, expect throw error', (done) => { + // setup + sinon.stub(helper, 'extractUpgradeInformation').throws(new CLiError(TEST_ERROR)); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal(TEST_ERROR); + expect(errorStub.args[0][0].message).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| helper preview upgrade fails, expect throw error', (done) => { + // setup + sinon.stub(helper, 'extractUpgradeInformation'); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, TEST_ERROR); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err).equal(TEST_ERROR); + expect(errorStub.args[0][0]).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| helper preview Upgrade without previewConfirm, expect throw information', (done) => { + // setup + sinon.stub(helper, 'extractUpgradeInformation'); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, null); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err).equal(undefined); + expect(infoStub.args[0][0]).equal('Command upgrade-project aborted.'); + expect(errorStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + }); + + describe('command handle - create V2 hosted skill project', () => { + let instance; + + beforeEach(() => { + instance = new UpgradeProjectCommand(optionModel); + sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('| helper move old project to legacy folder fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + isHosted: true, + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder').throws(new CLiError(TEST_ERROR)); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal(TEST_ERROR); + expect(errorStub.args[0][0].message).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| hostedSkillHelper create V2 project skeleton fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + isHosted: true, + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(hostedSkillHelper, 'createV2ProjectSkeleton').throws(new CLiError(TEST_ERROR)); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal(TEST_ERROR); + expect(errorStub.args[0][0].message).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| hostedSkillHelper download skill package fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + isHosted: true, + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(hostedSkillHelper, 'createV2ProjectSkeleton'); + sinon.stub(path, 'join').returns(FIXTURE_HOSTED_SKILL_RESOURCES_CONFIG); + sinon.stub(hostedSkillHelper, 'downloadSkillPackage').callsArgWith(5, TEST_ERROR); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err).equal(TEST_ERROR); + expect(errorStub.args[0][0]).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| hostedSkillHelper handle existing Lambda code fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + isHosted: true, + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(hostedSkillHelper, 'createV2ProjectSkeleton'); + sinon.stub(path, 'join').returns(FIXTURE_HOSTED_SKILL_RESOURCES_CONFIG); + sinon.stub(hostedSkillHelper, 'downloadSkillPackage').callsArgWith(5, null); + sinon.stub(hostedSkillHelper, 'handleExistingLambdaCode').throws(new CLiError(TEST_ERROR)); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal(TEST_ERROR); + expect(errorStub.args[0][0].message).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| hosted skill project migration succeeds , expect no error thrown', (done) => { + // setup + const TEST_USER_INPUT = { + isHosted: true, + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(hostedSkillHelper, 'createV2ProjectSkeleton'); + sinon.stub(path, 'join').returns(FIXTURE_HOSTED_SKILL_RESOURCES_CONFIG); + sinon.stub(hostedSkillHelper, 'downloadSkillPackage').callsArgWith(5, null); + sinon.stub(hostedSkillHelper, 'handleExistingLambdaCode'); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err).equal(undefined); + expect(infoStub.args[0][0]).equal('Project migration finished.'); + expect(errorStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + }); + + describe('command handle - create V2 Non hosted skill project', () => { + let instance; + + beforeEach(() => { + instance = new UpgradeProjectCommand(optionModel); + sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('| helper move old project to legacy folder fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder').throws(new CLiError(TEST_ERROR)); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal(TEST_ERROR); + expect(errorStub.args[0][0].message).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| helper create V2 project skeleton fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(helper, 'createV2ProjectSkeleton').throws(new CLiError(TEST_ERROR)); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal(TEST_ERROR); + expect(errorStub.args[0][0].message).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| helper download skill package fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(helper, 'createV2ProjectSkeleton'); + sinon.stub(path, 'join').returns(FIXTURE_RESOURCES_CONFIG); + sinon.stub(helper, 'downloadSkillPackage').callsArgWith(5, TEST_ERROR); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err).equal(TEST_ERROR); + expect(errorStub.args[0][0]).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| hostedSkillHelper handle existing Lambda code fails, expect throw error', (done) => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(helper, 'createV2ProjectSkeleton'); + sinon.stub(path, 'join').returns(FIXTURE_RESOURCES_CONFIG); + sinon.stub(helper, 'downloadSkillPackage').callsArgWith(5, null); + sinon.stub(helper, 'handleExistingLambdaCode').throws(new CLiError(TEST_ERROR)); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err.message).equal(TEST_ERROR); + expect(errorStub.args[0][0].message).equal(TEST_ERROR); + expect(infoStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + + it('| hosted skill project migration succeeds , expect no error thrown', (done) => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID + }; + sinon.stub(helper, 'extractUpgradeInformation').returns(TEST_USER_INPUT); + sinon.stub(helper, 'previewUpgrade').callsArgWith(1, null, true); + sinon.stub(helper, 'moveOldProjectToLegacyFolder'); + sinon.stub(helper, 'createV2ProjectSkeleton'); + sinon.stub(path, 'join').returns(FIXTURE_RESOURCES_CONFIG); + sinon.stub(helper, 'downloadSkillPackage').callsArgWith(5, null); + sinon.stub(helper, 'handleExistingLambdaCode'); + // call + instance.handle(TEST_CMD, (err) => { + // verify + expect(err).equal(undefined); + expect(infoStub.args[0][0]).equal('Project migration finished.'); + expect(errorStub.callCount).equal(0); + expect(warnStub.callCount).equal(0); + done(); + }); + }); + }); + }); +}); diff --git a/test/unit/commands/util/upgrade-project/ui-test.js b/test/unit/commands/util/upgrade-project/ui-test.js new file mode 100644 index 00000000..5f9bf52e --- /dev/null +++ b/test/unit/commands/util/upgrade-project/ui-test.js @@ -0,0 +1,159 @@ +const { expect } = require('chai'); +const inquirer = require('inquirer'); +const path = require('path'); +const sinon = require('sinon'); + +const Messenger = require('@src/view/messenger'); +const CONSTANTS = require('@src/utils/constants'); + +const ui = require('@src/commands/util/upgrade-project/ui'); + +function validateInquirerConfig(stub, expectedConfig) { + const { message, type, defaultValue, choices } = expectedConfig; + expect(stub.message).equal(message); + expect(stub.type).equal(type); + if (defaultValue) { + expect(stub.default).equal(defaultValue); + } + if (choices) { + expect(stub.choices).deep.equal(choices); + } +} + +describe('Commands upgrade-project test - UI test', () => { + const TEST_ERROR = 'upgrade-project error'; + const TEST_REGION = 'default'; + const TEST_REGION_NA = 'NA'; + const TEST_CODE_URI = 'codeUri'; + const TEST_V2_CODE_URI = `${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}/${TEST_CODE_URI}`; + const TEST_RUNTIME = 'runtime'; + const TEST_HANDLER = 'handler'; + const TEST_REVISION_ID = 'revisionId'; + const TEST_ARN = 'arn:aws:lambda:us-west-2:123456789012:function:ask-custom-skill-sample-nodejs-fact-default'; + + new Messenger({}); + let infoStub; + + describe('# validate ui.displayPreview', () => { + const TEST_SKILL_ID = 'skillId'; + const TEST_UPDATE_PREVIEW_PART_1 = `Preview of the upgrade result from v1 to v2: +- The original v1 skill project will be entirely moved to .${path.sep}${CONSTANTS.FILE_PATH.LEGACY_PATH}${path.sep} +- JSON files for Skill ID ${TEST_SKILL_ID} (such as skill.json) will be moved to .${path.sep}${CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE}${path.sep} +`; + + beforeEach(() => { + infoStub = sinon.stub(); + sinon.stub(Messenger, 'getInstance').returns({ + info: infoStub + }); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('| Preview hosted skill project display', () => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID, + isHosted: true + }; + const TEST_PART_2 = `- Existing Lambda codebase will be moved into "${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}" folder`; + // call + ui.displayPreview(TEST_USER_INPUT); + // verify + expect(infoStub.args[0][0]).equal(TEST_UPDATE_PREVIEW_PART_1 + TEST_PART_2); + }); + + it('| Preview no lambda project display', () => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID, + isHosted: false + }; + const TEST_PART_2 = '- No existing Lambda in the v1 "lambda" resource thus no action for Lambda codebase.'; + // call + ui.displayPreview(TEST_USER_INPUT); + // verify + expect(infoStub.args[0][0]).equal(TEST_UPDATE_PREVIEW_PART_1 + TEST_PART_2); + }); + + it('| Preview multiple regions project display', () => { + // setup + const TEST_USER_INPUT = { + skillId: TEST_SKILL_ID, + isHosted: false, + lambdaResources: { + [TEST_REGION]: { + arn: TEST_ARN, + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + v2CodeUri: `./${TEST_V2_CODE_URI}`, + revisionId: TEST_REVISION_ID + }, + [TEST_REGION_NA]: { + codeUri: TEST_CODE_URI, + runtime: TEST_RUNTIME, + handler: TEST_HANDLER, + v2CodeUri: `./${TEST_V2_CODE_URI}`, + revisionId: TEST_REVISION_ID + } + } + }; + const TEST_PART_2 = `- Existing Lambda codebase will be moved into "${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}" folder`; + const TEST_PART_2_1 = `\n - Region ${TEST_REGION}: v1 "${TEST_CODE_URI}"\ + -> v2 "./${TEST_V2_CODE_URI}" for existing Lambda ARN ${TEST_ARN}`; + const TEST_PART_2_2 = `\n - Region ${TEST_REGION_NA}: v1 "${TEST_CODE_URI}"\ + -> v2 "./${TEST_V2_CODE_URI}" and will create new Lambda`; + // call + ui.displayPreview(TEST_USER_INPUT); + // verify + expect(infoStub.args[0][0]).equal(TEST_UPDATE_PREVIEW_PART_1 + TEST_PART_2 + TEST_PART_2_1 + TEST_PART_2_2); + }); + }); + + describe('# validate ui.displayPreview', () => { + beforeEach(() => { + sinon.stub(inquirer, 'prompt'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('| confirm preview from user but error happens', (done) => { + // setup + inquirer.prompt.rejects(TEST_ERROR); + // call + ui.confirmPreview((err, response) => { + // verify + validateInquirerConfig(inquirer.prompt.args[0][0], { + message: 'Do you want to execute the upgrade based on the preview above? ', + type: 'confirm', + default: true, + }); + expect(err.name).equal(TEST_ERROR); + expect(response).equal(undefined); + done(); + }); + }); + + it('| confirm overwrite from user inputs', (done) => { + // setup + inquirer.prompt.resolves({ confirmPreview: true }); + // call + ui.confirmPreview((err, response) => { + // verify + validateInquirerConfig(inquirer.prompt.args[0][0], { + message: 'Do you want to execute the upgrade based on the preview above? ', + type: 'confirm', + default: true, + }); + expect(err).equal(null); + expect(response).equal(true); + done(); + }); + }); + }); +}); diff --git a/test/unit/commands/util/upgrade-to-v2/helper-test.js b/test/unit/commands/util/upgrade-to-v2/helper-test.js deleted file mode 100644 index 8c065b2f..00000000 --- a/test/unit/commands/util/upgrade-to-v2/helper-test.js +++ /dev/null @@ -1,377 +0,0 @@ -const { expect } = require('chai'); -const sinon = require('sinon'); -const fs = require('fs-extra'); -const path = require('path'); - -const helper = require('@src/commands/util/upgrade-to-v2/helper'); -const SkillMetadataController = require('@src/controllers/skill-metadata-controller'); -const Messenger = require('@src/view/messenger'); - -describe('Commands upgrade-to-v2 test - helper test', () => { - const TEST_PROFILE = 'default'; - const TEST_DO_DEBUG = false; - const TEST_SKILL_ID = 'skillId'; - const TEST_CODE_URI = 'codeUri'; - const TEST_RUNTIME = 'runtime'; - const TEST_HANDLER = 'handler'; - const TEST_ROOT_PATH = 'rootPath'; - - describe('# test helper method - extractUpgradeInformation', () => { - const TEST_V1_CONFIG_PATH = 'v1ConfigPath'; - const formV1Config = (skillId, isHosted, lambdaResources) => { - const result = { deploy_settings: {} }; - result.deploy_settings[TEST_PROFILE] = { - skill_id: skillId, - alexaHosted: { - isAlexaHostedSkill: isHosted - }, - resources: { - lambda: { - lambdaResources - } - } - }; - return result; - }; - - beforeEach(() => { - sinon.stub(path, 'join').returns(TEST_V1_CONFIG_PATH); - sinon.stub(fs, 'existsSync').returns(true); - sinon.stub(fs, 'readJsonSync').returns({}); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('| v1 project file does not exist, expect throw error', () => { - // setup - fs.existsSync.returns(false); - // call - try { - helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); - } catch (testError) { - // verify - expect(testError.message).equal('Failed to find ask-cli v1 project. ' - + 'Please make sure this command is called at the root of the skill project.'); - } - }); - - it('| skill ID does not exist, expect throw error message', () => { - // setup - fs.readJsonSync.returns(formV1Config(' ', false, {})); - // call - try { - helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); - } catch (testError) { - // verify - expect(testError.message).equal(`Failed to find skill_id for profile [${TEST_PROFILE}]. \ -If the skill has never been deployed in v1 ask-cli, please start from v2 structure.`); - } - }); - - it('| skill project is alexa hosted skill, expect quit with not support message', () => { - // setup - fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, true, {})); - // call - try { - helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); - } catch (testError) { - // verify - expect(testError.message).equal('Alexa Hosted Skill is currently not supported to upgrade.'); - } - }); - - describe('test helper method - extractUpgradeInformation : _validateLambdaResource', () => { - [ - { - testCase: '| validate lambda resources fails at alexaUsage, expect throw error message', - lambdas: [ - { alexaUsage: [] } - ], - expectError: 'Please make sure your alexaUsage is not empty.' - }, - { - testCase: '| validate lambda resources fails at codeUri, expect throw error message', - lambdas: [ - { - alexaUsage: ['custom/default'], - codeUri: ' ' - } - ], - expectError: 'Please make sure your codeUri is set to the path of your Lambda code.' - }, - { - testCase: '| validate lambda resources fails at runtime, expect throw error message', - lambdas: [ - { - alexaUsage: ['custom/default'], - codeUri: TEST_CODE_URI, - runtime: ' ' - } - ], - expectError: `Please make sure your runtime for codeUri ${TEST_CODE_URI} is set.` - }, - { - testCase: '| validate lambda resources fails at handler, expect throw error message', - lambdas: [ - { - alexaUsage: ['custom/default'], - codeUri: TEST_CODE_URI, - runtime: TEST_RUNTIME, - handler: '' - } - ], - expectError: `Please make sure your handler for codeUri ${TEST_CODE_URI} is set.` - } - ].forEach(({ testCase, lambdas, expectError }) => { - it(testCase, () => { - // setup - fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, false, lambdas)); - // call - try { - helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); - } catch (testError) { - // verify - expect(testError.message).equal(expectError); - } - }); - }); - }); - - describe('test helper method - extractUpgradeInformation : _collectLambdaMapFromResource', () => { - let warnStub; - - beforeEach(() => { - warnStub = sinon.stub(); - sinon.stub(Messenger, 'getInstance').returns({ - warn: warnStub - }); - }); - - it('| when no Lambda ARN exists, skip the upgrade with a warning message', () => { - // setup - const TEST_LAMBDAS = [ - { - alexaUsage: ['custom/default'], - codeUri: TEST_CODE_URI, - runtime: TEST_RUNTIME, - handler: TEST_HANDLER - } - ]; - fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, false, TEST_LAMBDAS)); - // call - try { - const info = helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); - // verify - expect(warnStub.args[0][0]).equal('Skip Lambda resource with alexaUsage "custom/default" since this Lambda is not deployed.'); - expect(info).deep.equal({ - skillId: TEST_SKILL_ID, - lambdaResources: {} - }); - } catch (testError) { - expect(testError).equal(undefined); - } - }); - - it('| when no Lambda ARN exists, skip the upgrade with a warning message', () => { - // setup - const TEST_LAMBDAS = [ - { - alexaUsage: ['custom/default'], - codeUri: TEST_CODE_URI, - runtime: TEST_RUNTIME, - handler: TEST_HANDLER - } - ]; - fs.readJsonSync.returns(formV1Config(TEST_SKILL_ID, false, TEST_LAMBDAS)); - // call - try { - const info = helper.extractUpgradeInformation(TEST_ROOT_PATH, TEST_PROFILE); - // verify - expect(warnStub.args[0][0]).equal('Skip Lambda resource with alexaUsage "custom/default" since this Lambda is not deployed.'); - expect(info).deep.equal({ - skillId: TEST_SKILL_ID, - lambdaResources: {} - }); - } catch (testError) { - expect(testError).equal(undefined); - } - }); - }); - }); - - describe('# test helper method - previewUpgrade', () => { - afterEach(() => { - sinon.restore(); - }); - - it('| skillCodeController buildSkillCode fails, expect callback error', (done) => { - // setup - sinon.stub(SkillCodeController.prototype, 'buildCode').callsArgWith(0, 'error'); - // call - helper.buildSkillCode(TEST_PROFILE, TEST_DO_DEBUG, (err, res) => { - // verify - expect(err).equal('error'); - expect(res).equal(undefined); - done(); - }); - }); - - it('| skillCodeController buildSkillCode passes, expect no error callback', (done) => { - // setup - sinon.stub(SkillCodeController.prototype, 'buildCode').callsArgWith(0); - // call - helper.buildSkillCode(TEST_PROFILE, TEST_DO_DEBUG, (err, res) => { - // verify - expect(err).equal(null); - expect(res).equal(undefined); - done(); - }); - }); - }); - - describe('# test helper method - moveOldProjectToLegacyFolder', () => { - afterEach(() => { - sinon.restore(); - }); - - it('| skillInfraController deploySkillInfrastructure fails, expect callback error', (done) => { - // setup - sinon.stub(SkillInfrastructureController.prototype, 'deployInfrastructure').callsArgWith(0, 'error'); - // call - helper.deploySkillInfrastructure(TEST_PROFILE, TEST_DO_DEBUG, (err, res) => { - // verify - expect(err).equal('error'); - expect(res).equal(undefined); - done(); - }); - }); - - it('| skillInfraController deploySkillInfrastructure passes, expect no error callback', (done) => { - // setup - sinon.stub(SkillInfrastructureController.prototype, 'deployInfrastructure').callsArgWith(0); - // call - helper.deploySkillInfrastructure(TEST_PROFILE, TEST_DO_DEBUG, (err, res) => { - // verify - expect(err).equal(undefined); - expect(res).equal(undefined); - done(); - }); - }); - }); - - describe('# test helper method - createV2ProjectSkeleton', () => { - let infoStub; - beforeEach(() => { - infoStub = sinon.stub(); - sinon.stub(Messenger, 'getInstance').returns({ - info: infoStub, - }); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('| skillMetaController enableSkill fails, expect callback error', (done) => { - // setup - sinon.stub(SkillMetadataController.prototype, 'enableSkill').callsArgWith(0, 'error'); - // call - helper.enableSkill(TEST_PROFILE, TEST_DO_DEBUG, (err) => { - expect(err).equal('error'); - expect(infoStub.args[0][0]).equal('\n==================== Enable Skill ===================='); - done(); - }); - }); - - it('| skillMetaController enableSkill passes, expect no error callback', (done) => { - // setup - sinon.stub(SkillMetadataController.prototype, 'enableSkill').callsArgWith(0); - // call - helper.enableSkill(TEST_PROFILE, TEST_DO_DEBUG, (err, res) => { - // verify - expect(err).equal(undefined); - expect(res).equal(undefined); - expect(infoStub.args[0][0]).equal('\n==================== Enable Skill ===================='); - done(); - }); - }); - }); - - describe('# test helper method - downloadSkillPackage', () => { - let infoStub; - beforeEach(() => { - infoStub = sinon.stub(); - sinon.stub(Messenger, 'getInstance').returns({ - info: infoStub, - }); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('| skillMetaController enableSkill fails, expect callback error', (done) => { - // setup - sinon.stub(SkillMetadataController.prototype, 'enableSkill').callsArgWith(0, 'error'); - // call - helper.enableSkill(TEST_PROFILE, TEST_DO_DEBUG, (err) => { - expect(err).equal('error'); - expect(infoStub.args[0][0]).equal('\n==================== Enable Skill ===================='); - done(); - }); - }); - - it('| skillMetaController enableSkill passes, expect no error callback', (done) => { - // setup - sinon.stub(SkillMetadataController.prototype, 'enableSkill').callsArgWith(0); - // call - helper.enableSkill(TEST_PROFILE, TEST_DO_DEBUG, (err, res) => { - // verify - expect(err).equal(undefined); - expect(res).equal(undefined); - expect(infoStub.args[0][0]).equal('\n==================== Enable Skill ===================='); - done(); - }); - }); - }); - - describe('# test helper method - handleExistingLambdaCode', () => { - let infoStub; - beforeEach(() => { - infoStub = sinon.stub(); - sinon.stub(Messenger, 'getInstance').returns({ - info: infoStub, - }); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('| skillMetaController enableSkill fails, expect callback error', (done) => { - // setup - sinon.stub(SkillMetadataController.prototype, 'enableSkill').callsArgWith(0, 'error'); - // call - helper.enableSkill(TEST_PROFILE, TEST_DO_DEBUG, (err) => { - expect(err).equal('error'); - expect(infoStub.args[0][0]).equal('\n==================== Enable Skill ===================='); - done(); - }); - }); - - it('| skillMetaController enableSkill passes, expect no error callback', (done) => { - // setup - sinon.stub(SkillMetadataController.prototype, 'enableSkill').callsArgWith(0); - // call - helper.enableSkill(TEST_PROFILE, TEST_DO_DEBUG, (err, res) => { - // verify - expect(err).equal(undefined); - expect(res).equal(undefined); - expect(infoStub.args[0][0]).equal('\n==================== Enable Skill ===================='); - done(); - }); - }); - }); -}); diff --git a/test/unit/fixture/model/upgrade-resources-config.json b/test/unit/fixture/model/upgrade-resources-config.json new file mode 100644 index 00000000..2bed4efd --- /dev/null +++ b/test/unit/fixture/model/upgrade-resources-config.json @@ -0,0 +1,91 @@ +{ + "askcliResourcesobjectVersion": "1.0", + "profiles": { + "default": { + "skillId": "amzn1.ask.skill.5555555-4444-3333-2222-1111111111", + "skillMetadata": { + "src": "./skill-package", + "lastDeployHash": "hash" + }, + "code": { + "default": { + "src": "lambda/codeUri" + }, + "NA": { + "src": "lambda/codeUri" + }, + "EU": { + "src": "./awsStack/lambda-EU/src", + "lastDeployHash": "" + }, + "FE": { + "src": "lambda/codeUri" + }, + "EI": { + "src": "lambda/codeUri" + }, + "IN": { + "src": "lambda/codeUri" + } + }, + "skillInfrastructure": { + "type": "@ask-cli/lambda-deployer", + "userConfig": { + "runtime": "runtime", + "handler": "handler", + "awsRegion": "us-west-2", + "regionalOverrides": { + "FE": { + "runtime": "JAVA", + "handler": "index.js" + }, + "IN": { + "runtime": "JAVA", + "handler": "index.js" + } + } + }, + "deployState": { + "default": { + "lambda": { + "arn": "arn:aws:lambda:us-west-2:123456789012:function:ask-custom-skill-sample-nodejs-fact-default", + "revisionId": "revisionId" + } + }, + "NA": { + "lambda": { + "arn": "arn:aws:lambda:us-west-2:123456789012:function:ask-custom-skill-sample-nodejs-fact-default", + "revisionId": "revisionId" + } + }, + "EU": { + "stackId": "", + "s3": { + "bucket": "", + "key": "endpoint/code", + "objectVersion": "" + } + }, + "FE": { + "lambda": { + "arn": "arn:aws:lambda:us-west-2:123456789012:function:ask-custom-skill-sample-nodejs-fact-default", + "revisionId": "revisionId" + } + }, + "EI": { + "lambda": { + "arn": "arn:aws:lambda:us-west-2:123456789012:function:ask-custom-skill-sample-nodejs-fact-default", + "revisionId": "revisionId" + } + }, + "IN": { + "lambda": { + "arn": "arn:aws:lambda:us-west-2:123456789012:function:ask-custom-skill-sample-nodejs-fact-default", + "revisionId": "revisionId" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/test/unit/run-test.js b/test/unit/run-test.js index 76d63325..5b35d720 100644 --- a/test/unit/run-test.js +++ b/test/unit/run-test.js @@ -49,9 +49,11 @@ require('module-alias/register'); '@test/unit/commands/smapi/customizations/smapi-hooks-test.js', '@test/unit/commands/smapi/hook-functions/append-vendor-id-test.js', '@test/unit/commands/smapi/hook-functions/map-testers-emails-test.js', - // command - export-package - '@test/unit/commands/api/skill-package/export-package/index-test.js', - '@test/unit/commands/api/skill-package/export-package/helper-test.js', + // command - util + '@test/unit/commands/util/upgrade-project/index-test', + '@test/unit/commands/util/upgrade-project/ui-test', + '@test/unit/commands/util/upgrade-project/helper-test', + '@test/unit/commands/util/upgrade-project/hosted-skill-helper-test', // clients '@test/unit/clients/http-client-test', '@test/unit/clients/metric-client-test', @@ -182,6 +184,9 @@ require('module-alias/register'); '@test/functional/commands/api/task/search-task', // metrics '@test/functional/commands/api/metrics/get-metrics-test', + // export-package + '@test/unit/commands/api/skill-package/export-package/index-test.js', + '@test/unit/commands/api/skill-package/export-package/helper-test.js', ].forEach((testFile) => { // eslint-disable-next-line global-require require(testFile);