ask-cli
Version:
Alexa Skills Kit (ASK) Command Line Interfaces
466 lines (434 loc) • 21.8 kB
JavaScript
const aws = require('aws-sdk');
const { expect } = require('chai');
const fs = require('fs');
const path = require('path');
const sinon = require('sinon');
const helper = require('@src/builtins/deploy-delegates/lambda-deployer/helper');
const IAMClient = require('@src/clients/aws-client/iam-client');
const LambdaClient = require('@src/clients/aws-client/lambda-client');
const Manifest = require('@src/model/manifest');
describe('Builtins test - lambda-deployer helper.js test', () => {
const FIXTURE_MANIFEST_FILE_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'manifest.json');
const TEST_PROFILE = 'default'; // test file uses 'default' profile
const TEST_IGNORE_HASH = false;
const TEST_AWS_PROFILE = 'default';
const TEST_AWS_REGION = 'us-east-1';
const TEST_ALEXA_REGION = 'default';
const TEST_SKILL_NAME = 'skill_name';
const TEST_SKILL_ID = 'skill_id';
const TEST_IAM_ROLE_ARN = 'iam_role_arn';
const TEST_LAMBDA_ARN = 'iam_lambda_arn';
const TEST_NO_SUCH_ENTITY_ERROR = {
code: 'NoSuchEntity'
};
const REPORTER = {
updateStatus: () => {}
};
const TEST_ROLE_DATA = {
Role: {
Arn: TEST_IAM_ROLE_ARN
}
};
describe('# test class method: validateLambdaDeployState', () => {
const TEST_LOCAL_IAM_ROLE = 'local_iam_role';
const TEST_LOCAL_REVISION_ID = 'local_revision_id';
const TEST_LOCAL_DEPLOY_STATE = {
iamRole: TEST_LOCAL_IAM_ROLE,
lambda: {
arn: TEST_LAMBDA_ARN,
revisionId: TEST_LOCAL_REVISION_ID
}
};
const TEST_REMOTE_IAM_ROLE = 'remote_iam_role';
const TEST_REMOTE_REVISION_ID = 'remote_revision_id';
afterEach(() => {
sinon.restore();
});
it('| no lambda arn found, expect original deploystate return', (done) => {
// setup
const TEST_DEPLOY_STATE_NO_LAMBDA = {};
// call
helper.validateLambdaDeployState(REPORTER, TEST_AWS_PROFILE, TEST_AWS_REGION, TEST_DEPLOY_STATE_NO_LAMBDA, TEST_IGNORE_HASH, (err, data) => {
// verify
expect(data.updatedDeployState).equal(TEST_DEPLOY_STATE_NO_LAMBDA);
done();
});
});
it('| an existing lambda arn found, getFunction request fails, expect an error return', (done) => {
// setup
const TEST_GET_FUNCTION_ERROR = 'get_function_error';
sinon.stub(LambdaClient.prototype, 'getFunction').callsArgWith(1, TEST_GET_FUNCTION_ERROR);
// call
helper.validateLambdaDeployState(REPORTER, TEST_AWS_PROFILE, TEST_AWS_REGION, TEST_LOCAL_DEPLOY_STATE, TEST_IGNORE_HASH, (err) => {
// verify
expect(err).equal(TEST_GET_FUNCTION_ERROR);
done();
});
});
it('| an existing lambda arn found, getFunction request passes, the local iam role does not equal with the remote iam role, '
+ 'expect an error return', (done) => {
// setup
const TEST_REMOTE_DEPLOY_STATE = {
Configuration: {
Role: TEST_REMOTE_IAM_ROLE,
RevisionId: TEST_REMOTE_REVISION_ID
}
};
const TEST_IAM_ROLE_ERROR = `The IAM role for Lambda ARN (${TEST_LAMBDA_ARN}) should be ${TEST_REMOTE_IAM_ROLE}, \
but found ${TEST_LOCAL_IAM_ROLE}. Please solve this IAM role mismatch and re-deploy again.`;
sinon.stub(LambdaClient.prototype, 'getFunction').callsArgWith(1, null, TEST_REMOTE_DEPLOY_STATE);
// call
helper.validateLambdaDeployState(REPORTER, TEST_AWS_PROFILE, TEST_AWS_REGION, TEST_LOCAL_DEPLOY_STATE, TEST_IGNORE_HASH, (err) => {
// verify
expect(err).equal(TEST_IAM_ROLE_ERROR);
done();
});
});
it('| an existing lambda arn found, getFunction request passes, the local revisionId does not equal with the remote revisionId,'
+ 'expect an error return', (done) => {
// setup
const TEST_REMOTE_DEPLOY_STATE = {
Configuration: {
Role: TEST_LOCAL_IAM_ROLE,
RevisionId: TEST_REMOTE_REVISION_ID
}
};
const TEST_REVISION_ID_ERROR = `The current revisionId (The revision ID for Lambda ARN (${TEST_LAMBDA_ARN}) should be \
${TEST_REMOTE_REVISION_ID}, but found ${TEST_LOCAL_REVISION_ID}. Please solve this revision mismatch and re-deploy again.`;
sinon.stub(LambdaClient.prototype, 'getFunction').callsArgWith(1, null, TEST_REMOTE_DEPLOY_STATE);
// call
helper.validateLambdaDeployState(REPORTER, TEST_AWS_PROFILE, TEST_AWS_REGION, TEST_LOCAL_DEPLOY_STATE, TEST_IGNORE_HASH, (err) => {
// verify
expect(err).equal(TEST_REVISION_ID_ERROR);
done();
});
});
it('| an existing lambda arn found, getFunction request passes, the local revisionId and remote revisionId compare is ignored'
+ 'expect updated deploy state return', (done) => {
// setup
const TEST_REMOTE_DEPLOY_STATE = {
Configuration: {
Role: TEST_LOCAL_IAM_ROLE,
RevisionId: TEST_REMOTE_REVISION_ID
}
};
const IGNORE_HASH = true;
sinon.stub(LambdaClient.prototype, 'getFunction').callsArgWith(1, null, TEST_REMOTE_DEPLOY_STATE);
// call
helper.validateLambdaDeployState(REPORTER, TEST_AWS_PROFILE, TEST_AWS_REGION, TEST_LOCAL_DEPLOY_STATE, IGNORE_HASH, (err, data) => {
// verify
expect(data.updatedDeployState.iamRole).equal(TEST_LOCAL_IAM_ROLE);
done();
});
});
it('| an existing lambda arn found, getFunction request passes, expect updated deploy state return', (done) => {
// setup
const TEST_REMOTE_DEPLOY_STATE = {
Configuration: {
Role: TEST_LOCAL_IAM_ROLE,
RevisionId: TEST_LOCAL_REVISION_ID
}
};
sinon.stub(LambdaClient.prototype, 'getFunction').callsArgWith(1, null, TEST_REMOTE_DEPLOY_STATE);
// call
helper.validateLambdaDeployState(REPORTER, TEST_AWS_PROFILE, TEST_AWS_REGION, TEST_LOCAL_DEPLOY_STATE, TEST_IGNORE_HASH, (err, data) => {
// verify
expect(data.updatedDeployState.iamRole).equal(TEST_LOCAL_IAM_ROLE);
expect(data.updatedDeployState.lambda.revisionId).equal(TEST_LOCAL_REVISION_ID);
done();
});
});
});
describe('# test class method: deployIAMRole', () => {
const TEST_DEPLOYSTATE_NO_ROLE = {
};
const TEST_DEPLOYSTATE = {
iamRole: TEST_IAM_ROLE_ARN
};
afterEach(() => {
sinon.restore();
});
it('| an existing IAM role found, get IAM Role fails, expect an error return', (done) => {
// setup
const TEST_ERROR = 'getRole error message';
sinon.stub(IAMClient.prototype, 'getIAMRole').callsArgWith(1, TEST_ERROR);
// call
helper.deployIAMRole(REPORTER, TEST_AWS_PROFILE, TEST_ALEXA_REGION, TEST_SKILL_NAME, TEST_AWS_REGION, TEST_DEPLOYSTATE, (err) => {
// verify
expect(err).equal(TEST_ERROR);
done();
});
});
it('| an existing IAM role found, get IAM Role throw "NoSuchEntity" error, expect an error return', (done) => {
// setup
const TEST_GET_ROLE_ERROR = `The IAM role is not found. Please check if your IAM role from region ${TEST_ALEXA_REGION} is valid.`;
sinon.stub(IAMClient.prototype, 'getIAMRole').callsArgWith(1, TEST_NO_SUCH_ENTITY_ERROR);
// call
helper.deployIAMRole(REPORTER, TEST_AWS_PROFILE, TEST_ALEXA_REGION, TEST_SKILL_NAME, TEST_AWS_REGION, TEST_DEPLOYSTATE, (err) => {
// verify
expect(err).equal(TEST_GET_ROLE_ERROR);
done();
});
});
it('| an existing IAM role found, get IAM Role passes, expect correct data return', (done) => {
// setup
sinon.stub(IAMClient.prototype, 'getIAMRole').callsArgWith(1, null, TEST_ROLE_DATA);
// call
helper.deployIAMRole(REPORTER, TEST_AWS_PROFILE, TEST_ALEXA_REGION, TEST_SKILL_NAME, TEST_AWS_REGION, TEST_DEPLOYSTATE, (err, res) => {
// verify
expect(res).equal(TEST_IAM_ROLE_ARN);
done();
});
});
it('| no IAM role found, create IAM role fails, expect an error return', (done) => {
// setup
const TEST_CREATE_ROLE_ERROR = 'createIAMRole error message';
sinon.stub(IAMClient.prototype, 'createBasicLambdaRole').callsArgWith(1, TEST_CREATE_ROLE_ERROR);
// call
helper.deployIAMRole(REPORTER, TEST_AWS_PROFILE, TEST_ALEXA_REGION, TEST_SKILL_NAME, TEST_AWS_REGION, TEST_DEPLOYSTATE_NO_ROLE, (err) => {
// verify
expect(err).equal(TEST_CREATE_ROLE_ERROR);
done();
});
});
it('| no IAM role found, create IAM role passes, attach role policy fails, expect an error return', (done) => {
// setup
const TEST_POLICY_ERROR = 'attachRolePolicy error message';
sinon.stub(IAMClient.prototype, 'createBasicLambdaRole').callsArgWith(1, null, TEST_ROLE_DATA);
sinon.stub(IAMClient.prototype, 'attachBasicLambdaRolePolicy').callsArgWith(1, TEST_POLICY_ERROR);
// call
helper.deployIAMRole(REPORTER, TEST_AWS_PROFILE, TEST_ALEXA_REGION, TEST_SKILL_NAME, TEST_AWS_REGION, TEST_DEPLOYSTATE_NO_ROLE, (err) => {
// verify
expect(err).equal(TEST_POLICY_ERROR);
done();
});
});
it('| no IAM role found, create IAM role and attach role policy passes, expect a IAM role arn return.', (done) => {
// setup
sinon.stub(IAMClient.prototype, 'createBasicLambdaRole').callsArgWith(1, null, TEST_ROLE_DATA);
sinon.stub(IAMClient.prototype, 'attachBasicLambdaRolePolicy').callsArgWith(1, null, TEST_IAM_ROLE_ARN);
// call
helper.deployIAMRole(REPORTER, TEST_AWS_PROFILE, TEST_ALEXA_REGION, TEST_SKILL_NAME, TEST_AWS_REGION, TEST_DEPLOYSTATE_NO_ROLE,
(err, res) => {
// verify
expect(res).equal(TEST_IAM_ROLE_ARN);
done();
});
});
});
describe('# test class method: deployLambdaFunction', () => {
const TEST_ZIP_FILE_PATH = 'zip_file_path';
const TEST_ZIP_FILE = 'zip_file';
const TEST_CODE_BUILD = 'code_build_path';
const TEST_LAST_MODIFIED = 'last_modified_time';
const TEST_REVISION_ID = 'revision_id';
const TEST_UPDATED_REVISION_ID = 'revision id';
const TEST_FUNCTION_ARN = 'lambda function arn';
const TEST_RUNTIME = 'runtime';
const TEST_HANDLER = 'handler';
const TEST_CREATE_OPTIONS = {
profile: TEST_PROFILE,
awsProfile: TEST_AWS_REGION,
alexaRegion: TEST_ALEXA_REGION,
awsRegion: TEST_AWS_REGION,
skillId: TEST_SKILL_ID,
skillName: TEST_SKILL_NAME,
code: { codeBuild: TEST_CODE_BUILD },
iamRoleArn: TEST_IAM_ROLE_ARN,
zipFilePath: TEST_ZIP_FILE_PATH,
userConfig: { awsRegion: TEST_AWS_REGION },
currentRegionDeployState: {}
};
const TEST_UPDATE_OPTIONS = {
profile: TEST_PROFILE,
awsProfile: TEST_PROFILE,
alexaRegion: TEST_ALEXA_REGION,
awsRegion: TEST_AWS_REGION,
skillId: TEST_SKILL_ID,
skillName: TEST_SKILL_NAME,
code: { codeBuild: TEST_CODE_BUILD },
iamRoleArn: TEST_IAM_ROLE_ARN,
zipFilePath: TEST_ZIP_FILE_PATH,
userConfig: { awsRegion: TEST_AWS_REGION, runtime: TEST_RUNTIME, handler: TEST_HANDLER },
currentRegionDeployState: { lambda: {
arn: TEST_LAMBDA_ARN,
lastModified: TEST_LAST_MODIFIED,
revisionId: TEST_REVISION_ID
} }
};
beforeEach(() => {
new Manifest(FIXTURE_MANIFEST_FILE_PATH);
});
afterEach(() => {
Manifest.dispose();
sinon.restore();
});
it('| no Lambda found, create Lambda function fails, expect an error return.', (done) => {
// setup
const TEST_CREATE_FUNCTION_ERROR = 'createLambdaFunction error';
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
sinon.stub(LambdaClient.prototype, 'createLambdaFunction').callsArgWith(7, TEST_CREATE_FUNCTION_ERROR);
// call
helper.deployLambdaFunction(REPORTER, TEST_CREATE_OPTIONS, (err) => {
// verify
expect(err).equal(TEST_CREATE_FUNCTION_ERROR);
done();
});
});
it('| no Lambda found, create Lambda function fails and get retry message, retry passes after one retry,'
+ ' add Alexa Permission fails, expect an error return.', (done) => {
// setup
const RETRY_MESSAGE = 'The role defined for the function cannot be assumed by Lambda.';
const TEST_CREATE_FUNCTION_ERROR = {
message: RETRY_MESSAGE
};
const TEST_LAMBDA_DATA = {
FunctionArn: TEST_FUNCTION_ARN
};
const TEST_ADD_PERMISSION_ERROR = 'addAlexaPermissionByDomain error';
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
const stubTestFunc = sinon.stub(LambdaClient.prototype, 'createLambdaFunction');
stubTestFunc.onCall(0).callsArgWith(7, TEST_CREATE_FUNCTION_ERROR);
stubTestFunc.onCall(1).callsArgWith(7, null, TEST_LAMBDA_DATA);
sinon.stub(LambdaClient.prototype, 'addAlexaPermissionByDomain').callsArgWith(3, TEST_ADD_PERMISSION_ERROR);
// call
helper.deployLambdaFunction(REPORTER, TEST_CREATE_OPTIONS, (err) => {
// verify
expect(err).equal(TEST_ADD_PERMISSION_ERROR);
done();
});
}).timeout(10000);
it('| no Lambda found, create Lambda function passes, add Alexa Permission fails, expect an error return.', (done) => {
// setup
const TEST_LAMBDA_DATA = {
FunctionArn: TEST_FUNCTION_ARN
};
const TEST_ADD_PERMISSION_ERROR = 'addAlexaPermissionByDomain error';
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
sinon.stub(LambdaClient.prototype, 'createLambdaFunction').callsArgWith(7, null, TEST_LAMBDA_DATA);
sinon.stub(LambdaClient.prototype, 'addAlexaPermissionByDomain').callsArgWith(3, TEST_ADD_PERMISSION_ERROR);
// call
helper.deployLambdaFunction(REPORTER, TEST_CREATE_OPTIONS, (err) => {
// verify
expect(err).equal(TEST_ADD_PERMISSION_ERROR);
done();
});
});
it('| no Lambda found, create Lambda function and add Alexa Permission pass, get Function revisionId fails,'
+ ' expect an error return.', (done) => {
// setup
const TEST_LAMBDA_DATA = {
FunctionArn: TEST_FUNCTION_ARN
};
const TEST_REVISION_ID_ERROR = 'getFunctionRevisionId error';
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
sinon.stub(LambdaClient.prototype, 'createLambdaFunction').callsArgWith(7, null, TEST_LAMBDA_DATA);
sinon.stub(LambdaClient.prototype, 'addAlexaPermissionByDomain').callsArgWith(3, null);
sinon.stub(LambdaClient.prototype, 'getFunction').callsArgWith(1, TEST_REVISION_ID_ERROR);
// call
helper.deployLambdaFunction(REPORTER, TEST_CREATE_OPTIONS, (err) => {
// verify
expect(err).equal(TEST_REVISION_ID_ERROR);
done();
});
});
it('| no Lambda found, create Lambda function, add Alexa Permission and get Function revisionId pass, expect Lambda data return.', (done) => {
// setup
const TEST_LAMBDA_DATA = {
FunctionArn: TEST_FUNCTION_ARN,
LastModified: TEST_LAST_MODIFIED
};
const TEST_GET_FUNCTION_RESPONSE = {
Configuration: {
RevisionId: TEST_UPDATED_REVISION_ID,
}
};
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
sinon.stub(LambdaClient.prototype, 'createLambdaFunction').callsArgWith(7, null, TEST_LAMBDA_DATA);
sinon.stub(LambdaClient.prototype, 'addAlexaPermissionByDomain').callsArgWith(3, null);
sinon.stub(LambdaClient.prototype, 'getFunction').callsArgWith(1, null, TEST_GET_FUNCTION_RESPONSE);
// call
helper.deployLambdaFunction(REPORTER, TEST_CREATE_OPTIONS, (err, res) => {
// verify
expect(res).deep.equal({
isAllStepSuccess: true,
isCodeDeployed: true,
lambdaResponse: {
arn: TEST_FUNCTION_ARN,
lastModified: TEST_LAST_MODIFIED,
revisionId: TEST_UPDATED_REVISION_ID
}
});
done();
});
});
it('| an existing Lambda found, update function code fails, expect an error return.', (done) => {
// setup
const TEST_UPDATE_CODE_ERROR = 'updateFunctionCode error';
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
sinon.stub(LambdaClient.prototype, 'updateFunctionCode').callsArgWith(3, TEST_UPDATE_CODE_ERROR);
// call
helper.deployLambdaFunction(REPORTER, TEST_UPDATE_OPTIONS, (err) => {
// verify
expect(err).equal(TEST_UPDATE_CODE_ERROR);
done();
});
});
it('| an existing Lambda found, update function code passes, update function configuration fails, expect an error return.', (done) => {
// setup
const TEST_UPDATE_CONGIF_ERROR = 'updateFunctionConfiguration error';
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
sinon.stub(LambdaClient.prototype, 'updateFunctionCode').callsArgWith(3, null, { RevisionId: TEST_REVISION_ID });
sinon.stub(LambdaClient.prototype, 'updateFunctionConfiguration').callsArgWith(4, TEST_UPDATE_CONGIF_ERROR);
// call
helper.deployLambdaFunction(REPORTER, TEST_UPDATE_OPTIONS, (err, res) => {
// verify
expect(err).equal(null);
expect(res).deep.equal({
isAllStepSuccess: false,
isCodeDeployed: true,
lambdaResponse: {
arn: undefined,
lastModified: undefined,
revisionId: TEST_REVISION_ID
},
resultMessage: TEST_UPDATE_CONGIF_ERROR
});
done();
});
});
it('| an existing Lambda found, update function code and update function configuration pass, expect updated lambda data return.', (done) => {
// setup
const TEST_UPDATE_CONFIG_DATA = {
FunctionArn: TEST_LAMBDA_ARN,
LastModified: TEST_LAST_MODIFIED,
RevisionId: TEST_REVISION_ID
};
sinon.stub(fs, 'readFileSync').withArgs(TEST_ZIP_FILE_PATH).returns(TEST_ZIP_FILE);
sinon.stub(aws, 'Lambda');
sinon.stub(LambdaClient.prototype, 'updateFunctionCode').callsArgWith(3, null, { RevisionId: TEST_REVISION_ID });
sinon.stub(LambdaClient.prototype, 'updateFunctionConfiguration').callsArgWith(4, null, TEST_UPDATE_CONFIG_DATA);
// call
helper.deployLambdaFunction(REPORTER, TEST_UPDATE_OPTIONS, (err, data) => {
// verify
expect(data).deep.equal({
isAllStepSuccess: true,
isCodeDeployed: true,
lambdaResponse: {
arn: TEST_LAMBDA_ARN,
lastModified: TEST_LAST_MODIFIED,
revisionId: TEST_REVISION_ID
}
});
done();
});
});
});
});