'use strict'; const path = require('path'); const fs = require('fs'); const fse = require('fs-extra'); const stripAnsi = require('strip-ansi'); const { expect } = require('chai'); const log = require('log').get('serverless:test'); const spawn = require('child-process-ext/spawn'); const resolveAwsEnv = require('@serverless/test/resolve-aws-env'); const hasFailed = require('@serverless/test/has-failed'); const awsRequest = require('@serverless/test/aws-request'); const CloudFormationService = require('aws-sdk').CloudFormation; const { getTmpDirPath } = require('./utils/fs'); const serverlessExec = require('./serverless-binary'); describe('Service Lifecyle Integration Test', function () { this.timeout(1000 * 60 * 10); // Involves time-taking deploys const templateName = 'aws-nodejs'; const tmpDir = getTmpDirPath(); const env = resolveAwsEnv(); const spawnOptions = { cwd: tmpDir, env, // As in invoke we optionally read stdin, we need to ensure it's closed // See https://github.com/sindresorhus/get-stdin/issues/13#issuecomment-279234249 shouldCloseStdin: true, }; let serviceName; let StackName; before(() => { serviceName = `test-basic-${process.hrtime()[1]}`; StackName = `${serviceName}-dev`; log.notice(`Temporary path: ${tmpDir}`); fse.mkdirsSync(tmpDir); }); // Do not continue if any of the tests failed beforeEach(function () { if (hasFailed(this.test.parent)) this.skip(); }); after(async () => { try { await awsRequest(CloudFormationService, 'describeStacks', { StackName }); } catch (error) { if (error.message.indexOf('does not exist') > -1) return; throw error; } await spawn(serverlessExec, ['remove'], { cwd: tmpDir, env }); }); it('should create service in tmp directory', async () => { await spawn( serverlessExec, ['create', '--template', templateName, '--name', serviceName], spawnOptions ); expect(fs.existsSync(path.join(tmpDir, 'serverless.yml'))).to.be.equal(true); expect(fs.existsSync(path.join(tmpDir, 'handler.js'))).to.be.equal(true); }); it('should deploy service to aws', async () => { await spawn(serverlessExec, ['deploy'], { cwd: tmpDir, env }); const d = await awsRequest(CloudFormationService, 'describeStacks', { StackName }); expect(d.Stacks[0].StackStatus).to.be.equal('UPDATE_COMPLETE'); }); it('should invoke function from aws', async () => { const { stdoutBuffer: invoked } = await spawn( serverlessExec, ['invoke', '--function', 'hello'], spawnOptions ); const result = JSON.parse(invoked); // parse it once again because the body is stringified to be LAMBDA-PROXY ready const message = JSON.parse(result.body).message; expect(message).to.be.equal('Go Serverless v1.0! Your function executed successfully!'); }); it('should deploy updated service to aws', () => { const newHandler = ` 'use strict'; module.exports.hello = (event, context, cb) => cb(null, { message: 'Service Update Succeeded' } ); `; fs.writeFileSync(path.join(tmpDir, 'handler.js'), newHandler); return spawn(serverlessExec, ['deploy'], spawnOptions); }); it('should invoke updated function from aws', async () => { const { stdoutBuffer: invoked } = await spawn( serverlessExec, ['invoke', '--function', 'hello'], spawnOptions ); const result = JSON.parse(invoked); expect(result.message).to.be.equal('Service Update Succeeded'); }); it('should list existing deployments and roll back to first deployment', async () => { let timestamp; const { stdoutBuffer: listDeploys } = await spawn( serverlessExec, ['deploy', 'list'], spawnOptions ); const output = stripAnsi(listDeploys.toString()); const match = output.match(new RegExp('Timestamp: (.+)')); if (match) { timestamp = match[1]; } expect(timestamp).to.not.undefined; await spawn(serverlessExec, ['rollback', '-t', timestamp], { cwd: tmpDir, env }); const { stdoutBuffer: invoked } = await spawn( serverlessExec, ['invoke', '--function', 'hello'], spawnOptions ); const result = JSON.parse(invoked); // parse it once again because the body is stringified to be LAMBDA-PROXY ready const message = JSON.parse(result.body).message; expect(message).to.be.equal('Go Serverless v1.0! Your function executed successfully!'); }); it('should remove service from aws', async () => { await spawn(serverlessExec, ['remove'], { cwd: tmpDir, env }); const d = await (async () => { try { return await awsRequest(CloudFormationService, 'describeStacks', { StackName }); } catch (error) { if (error.message.indexOf('does not exist') > -1) return null; throw error; } })(); if (!d) return; expect(d.Stacks[0].StackStatus).to.be.equal('DELETE_COMPLETE'); }); });