'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') }) })