├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── Server.js ├── endpoints │ ├── AlexaSkillEndpoint.js │ ├── Endpoint.js │ ├── HttpEndpoint.js │ └── get.js └── index.js └── test ├── index.js └── lambda-handler.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.log 3 | .nyc_output/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.10' 4 | - '8.10' 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 DieProduktMacher GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Serverless Local Dev Server Plugin (Beta) 2 | ======= 3 | 4 | [![Build Status](https://travis-ci.org/DieProduktMacher/serverless-local-dev-server.svg?branch=develop)](https://travis-ci.org/DieProduktMacher/serverless-local-dev-server) 5 | 6 | This plugin exposes Alexa-Skill and HTTP events as local HTTP endpoints, removing the need to deploy every code change to AWS Lambda. You can connect these endpoints to Alexa, Facebook Messenger or other services via forwardhq, ngrok or any other forwarding service. 7 | 8 | Supported features: 9 | 10 | * Expose `alexa-skill` and `http` events as local HTTP endpoints 11 | * Environment variables 12 | * Basic HTTP integration 13 | * Auto reload via nodemon (see *How To*) 14 | 15 | This package requires node >= 6.0 16 | 17 | 18 | # How To 19 | 20 | ### 1. Install the plugin 21 | 22 | ```sh 23 | npm install serverless-local-dev-server --save-dev 24 | ``` 25 | 26 | ### 2. Add the plugin to your serverless configuration file 27 | 28 | *serverless.yml* configuration example: 29 | 30 | ```yaml 31 | provider: 32 | name: aws 33 | runtime: nodejs6.10 34 | 35 | functions: 36 | hello: 37 | handler: handler.hello 38 | events: 39 | - alexaSkill 40 | - http: GET /hello 41 | 42 | # Add serverless-local-dev-server to your plugins: 43 | plugins: 44 | - serverless-local-dev-server 45 | 46 | # if needed add folder for serving static files if necessary (relative to service path) 47 | custom: 48 | localDevStaticFolder: path/to/static/files 49 | ``` 50 | 51 | 52 | 53 | ### 3. Start the server 54 | 55 | ```sh 56 | serverless local-dev-server 57 | ``` 58 | 59 | On default the server listens on port 5005. You can specify another one with the *--port* argument: 60 | 61 | ```sh 62 | serverless local-dev-server --port 5000 63 | ``` 64 | 65 | To automatically restart the server when files change, you may use nodemon: 66 | 67 | ```sh 68 | nodemon --exec "serverless local-dev-server" -e "js yml json" 69 | ``` 70 | 71 | To see responses returned from Lambda and stack traces, prepend SLS_DEBUG=* 72 | 73 | ```sh 74 | SLS_DEBUG=* serverless local-http-server 75 | ``` 76 | 77 | ### 4. For Alexa Skills 78 | 79 | #### 4.1 Share localhost with the internet 80 | 81 | For example with forwardhq: 82 | 83 | ```sh 84 | forward 5005 85 | ``` 86 | 87 | #### 4.2 Configure AWS to use your HTTPS endpoint 88 | 89 | In the Configuration pane, select HTTPS as service endpoint type and specify the forwarded endpoint URL. 90 | 91 | As method for SSL Certificate validation select *My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority*. 92 | 93 | 94 | # License & Credits 95 | 96 | Licensed under the MIT license. 97 | 98 | Created and maintained by [DieProduktMacher](http://www.dieproduktmacher.com). 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-local-dev-server", 3 | "version": "0.3.1", 4 | "engines": { 5 | "node": ">=6.10" 6 | }, 7 | "description": "Develop Alexa-Skill and HTTP functions in Serverless without deploying to AWS", 8 | "author": "DieProduktMacher ", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/DieProduktMacher/serverless-local-dev-server" 13 | }, 14 | "keywords": [ 15 | "serverless", 16 | "serverless-plugin", 17 | "alexa", 18 | "alexa-skill", 19 | "http", 20 | "development", 21 | "dev", 22 | "local", 23 | "aws-lambda" 24 | ], 25 | "main": "src/index.js", 26 | "scripts": { 27 | "test": "nyc mocha --exit", 28 | "lint": "standard" 29 | }, 30 | "dependencies": { 31 | "body-parser": "^1.18.3", 32 | "dotenv": "^4.0.0", 33 | "express": "^4.16.3" 34 | }, 35 | "devDependencies": { 36 | "chai": "^4.1.2", 37 | "chai-as-promised": "^7.1.1", 38 | "mocha": "^5.2.0", 39 | "node-fetch": "^1.7.3", 40 | "nyc": "^13.0.1", 41 | "serverless": "^1.29.2", 42 | "sinon": "^2.3.7", 43 | "standard": "^10.0.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Express = require('express') 4 | const BodyParser = require('body-parser') 5 | const path = require('path') 6 | const dotenv = require('dotenv') 7 | const getEndpoints = require('./endpoints/get') 8 | 9 | class Server { 10 | constructor () { 11 | this.functions = [] 12 | this.staticFolder = false 13 | this.log = console.log 14 | this.customEnvironment = {} 15 | // copy initial process.env 16 | this.processEnvironment = Object.assign({}, process.env) 17 | } 18 | // Starts the server 19 | start (port) { 20 | if (this.functions.length === 0) { 21 | this.log('No Lambdas with Alexa-Skill or HTTP events found') 22 | return 23 | } 24 | this.app = Express() 25 | this.app.use(BodyParser.json()) 26 | this.functions.forEach(func => 27 | func.endpoints.forEach(endpoint => this._attachEndpoint(func, endpoint)) 28 | ) 29 | if (this.staticFolder) { 30 | this.app.use('/static', Express.static(this.staticFolder)) 31 | } 32 | this.app.listen(port, _ => { 33 | this.log(`Listening on port ${port} for requests 🚀`) 34 | this.log('----') 35 | this.functions.forEach(func => { 36 | this.log(`${func.name}:`) 37 | func.endpoints.forEach(endpoint => { 38 | this.log(` ${endpoint.method} http://localhost:${port}${endpoint.path}`) 39 | }) 40 | }) 41 | if (this.staticFolder) { 42 | this.log(`StaticFolder`) 43 | this.log(` GET http://localhost:${port}/static (${this.staticFolder})`) 44 | this.app.use('/static', Express.static(this.staticFolder)) 45 | } 46 | this.log('----') 47 | }) 48 | } 49 | // Sets functions, including endpoints, using the serverless config and service path 50 | setConfiguration (serverlessConfig, servicePath) { 51 | this.functions = Object.keys(serverlessConfig.functions).map(name => { 52 | const functionConfig = serverlessConfig.functions[name] 53 | const [handlerSrcFile, handlerFunctionName] = functionConfig.handler.split('.') 54 | return { 55 | name: name, 56 | config: serverlessConfig.functions[name], 57 | handlerModulePath: path.join(servicePath, handlerSrcFile), 58 | handlerFunctionName, 59 | environment: Object.assign({}, serverlessConfig.provider.environment, functionConfig.environment, this.customEnvironment) 60 | } 61 | }).map(func => 62 | Object.assign({}, func, { endpoints: getEndpoints(func) }) 63 | ).filter(func => 64 | func.endpoints.length > 0 65 | ) 66 | if (serverlessConfig.custom.localDevStaticFolder) { 67 | this.staticFolder = path.join(servicePath, serverlessConfig.custom.localDevStaticFolder) 68 | } 69 | } 70 | // Attaches HTTP endpoint to Express 71 | _attachEndpoint (func, endpoint) { 72 | // Validate method and path 73 | /* istanbul ignore next */ 74 | if (!endpoint.method || !endpoint.path) { 75 | return this.log(`Endpoint ${endpoint.type} for function ${func.name} has no method or path`) 76 | } 77 | // Add HTTP endpoint to Express 78 | this.app[endpoint.method.toLowerCase()](endpoint.path, (request, response) => { 79 | this.log(`${endpoint}`) 80 | // Execute Lambda with corresponding event, forward response to Express 81 | let lambdaEvent = endpoint.getLambdaEvent(request) 82 | this._executeLambdaHandler(func, lambdaEvent).then(result => { 83 | this.log(' ➡ Success') 84 | if (process.env.SLS_DEBUG) console.info(result) 85 | endpoint.handleLambdaSuccess(response, result) 86 | }).catch(error => { 87 | this.log(` ➡ Failure: ${error.message}`) 88 | if (process.env.SLS_DEBUG) console.error(error.stack) 89 | endpoint.handleLambdaFailure(response, error) 90 | }) 91 | }) 92 | } 93 | // Loads and executes the Lambda handler 94 | _executeLambdaHandler (func, event) { 95 | return new Promise((resolve, reject) => { 96 | // Load local development environment variables 97 | const localEnvironment = Object.assign({}, 98 | dotenv.config({path: '.env.local'}).parsed, 99 | dotenv.config({path: '.local.env'}).parsed, 100 | dotenv.config({path: 'local.env'}).parsed) 101 | // set process.env explicitly 102 | process.env = Object.assign({}, this.processEnvironment, func.environment, localEnvironment) 103 | const handle = require(func.handlerModulePath)[func.handlerFunctionName] 104 | const context = { succeed: resolve, fail: reject } 105 | const callback = (error, result) => (!error) ? resolve(result) : reject(error) 106 | 107 | // Execute it! 108 | handle(event, context, callback) 109 | }) 110 | } 111 | } 112 | 113 | module.exports = Server 114 | -------------------------------------------------------------------------------- /src/endpoints/AlexaSkillEndpoint.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Endpoint = require('./Endpoint') 4 | 5 | class AlexaSkillEndpoint extends Endpoint { 6 | constructor (alexaSkillConfig, func) { 7 | super(alexaSkillConfig, func) 8 | this.name = func.name 9 | this.method = 'POST' 10 | this.path = `/alexa-skill/${this.name}` 11 | } 12 | getLambdaEvent (request) { 13 | // Pass-through 14 | return request.body 15 | } 16 | handleLambdaSuccess (response, result) { 17 | response.send(result) 18 | } 19 | toString () { 20 | return `Alexa-Skill: ${this.name}` 21 | } 22 | } 23 | 24 | module.exports = AlexaSkillEndpoint 25 | -------------------------------------------------------------------------------- /src/endpoints/Endpoint.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Endpoint { 4 | constructor (eventConfig, func) { 5 | this.method = 'GET' 6 | this.path = '' 7 | } 8 | /* istanbul ignore next */ 9 | getLambdaEvent (request) { 10 | return {} 11 | } 12 | /* istanbul ignore next */ 13 | handleLambdaSuccess (response, result) { 14 | response.sendStatus(204) 15 | } 16 | /* istanbul ignore next */ 17 | handleLambdaFailure (response, error) { 18 | response.sendStatus(500) 19 | } 20 | } 21 | 22 | module.exports = Endpoint 23 | -------------------------------------------------------------------------------- /src/endpoints/HttpEndpoint.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const Endpoint = require('./Endpoint') 5 | 6 | class HttpEndpoint extends Endpoint { 7 | constructor (httpConfig, func) { 8 | super(httpConfig, func) 9 | if (typeof httpConfig === 'string') { 10 | let s = httpConfig.split(' ') 11 | httpConfig = { method: s[0], path: s[1] } 12 | } 13 | this.method = httpConfig.method 14 | this.resourcePath = httpConfig.path.replace(/\{([a-zA-Z_]+)\}/g, ':$1') 15 | this.resource = httpConfig.path 16 | this.path = path.posix.join('/http', this.resourcePath) 17 | } 18 | getLambdaEvent (request) { 19 | const pathParameters = request.params || {} 20 | const path = this.resourcePath.replace(/:([a-zA-Z_]+)/g, (param) => pathParameters[param.substr(1)]) 21 | return { 22 | httpMethod: request.method, 23 | body: JSON.stringify(request.body, null, ' '), 24 | queryStringParameters: request.query, 25 | pathParameters, 26 | path, 27 | resource: this.resource 28 | } 29 | } 30 | handleLambdaSuccess (response, result) { 31 | if (result.headers) { 32 | response.set(result.headers) 33 | } 34 | response.status(result.statusCode) 35 | response.send(result.body === 'object' ? JSON.stringify(result.body) : result.body) 36 | } 37 | toString () { 38 | return `HTTP: ${this.method} ${this.resourcePath}` 39 | } 40 | } 41 | 42 | module.exports = HttpEndpoint 43 | -------------------------------------------------------------------------------- /src/endpoints/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const mappings = { 4 | 'alexaSkill': require('./AlexaSkillEndpoint'), 5 | 'http': require('./HttpEndpoint') 6 | } 7 | 8 | module.exports = (func) => { 9 | return func.config.events.map(event => { 10 | switch (typeof event) { 11 | case 'string': 12 | return { type: event, config: {} } 13 | case 'object': 14 | let type = Object.keys(event)[0] 15 | return { type: type, config: event[type] } 16 | /* istanbul ignore next */ 17 | default: 18 | return null 19 | } 20 | }).filter(_ => 21 | !!mappings[_.type] 22 | ).map(_ => { 23 | let endpoint = new mappings[_.type](_.config, func) 24 | endpoint.type = _.type 25 | return endpoint 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Server = require('./Server.js') 4 | 5 | class ServerlessLocalDevServerPlugin { 6 | constructor (serverless, options) { 7 | this.serverless = serverless 8 | this.options = options || {} 9 | 10 | this.commands = { 11 | 'local-dev-server': { 12 | usage: 'Runs a local dev server for Alexa-Skill and HTTP functions', 13 | lifecycleEvents: [ 'loadEnvVars', 'start' ], 14 | options: { 15 | port: { usage: 'Port to listen on', shortcut: 'p' } 16 | } 17 | } 18 | } 19 | 20 | this.hooks = { 21 | 'local-dev-server:loadEnvVars': this.loadEnvVars.bind(this), 22 | 'local-dev-server:start': this.start.bind(this) 23 | } 24 | } 25 | 26 | loadEnvVars () { 27 | Object.assign(process.env, { IS_LOCAL: true }) 28 | } 29 | 30 | start () { 31 | let server = new Server() 32 | server.log = this.serverless.cli.log.bind(this.serverless.cli) 33 | Object.assign(server.customEnvironment, this.options.environment) 34 | server.setConfiguration(this.serverless.service, this.serverless.config.servicePath) 35 | server.start(this.options.port || 5005) 36 | } 37 | } 38 | 39 | module.exports = ServerlessLocalDevServerPlugin 40 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global describe it beforeEach afterEach */ 4 | const chai = require('chai') 5 | const chaiAsPromised = require('chai-as-promised') 6 | const fetch = require('node-fetch') 7 | const sinon = require('sinon') 8 | const Serverless = require('serverless/lib/Serverless') 9 | const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider') 10 | const AlexaDevServer = require('../src') 11 | 12 | chai.use(chaiAsPromised) 13 | const expect = chai.expect 14 | 15 | describe('index.js', () => { 16 | let sandbox, serverless, alexaDevServer 17 | 18 | const sendAlexaRequest = (port, name) => { 19 | return fetch(`http://localhost:${port}/alexa-skill/${name}`, { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | 'Accept': 'application/json' 24 | }, 25 | body: '{"session":{},"request":{},"version":"1.0"}' 26 | }) 27 | } 28 | 29 | const sendHttpGetRequest = (port, path) => { 30 | return fetch(`http://localhost:${port}/http/${path}`) 31 | } 32 | 33 | const sendHttpPostRequest = (port, path) => { 34 | return fetch(`http://localhost:${port}/http/${path}`, { 35 | method: 'POST', 36 | headers: { 37 | 'Content-Type': 'application/json', 38 | 'Accept': 'application/json' 39 | }, 40 | body: '{"foo":"bar"}' 41 | }) 42 | } 43 | 44 | beforeEach(() => { 45 | sandbox = sinon.sandbox.create() 46 | serverless = new Serverless() 47 | serverless.init() 48 | serverless.setProvider('aws', new AwsProvider(serverless)) 49 | serverless.config.servicePath = __dirname 50 | }) 51 | 52 | afterEach((done) => { 53 | sandbox.restore() 54 | done() 55 | }) 56 | 57 | it('should have hooks', () => { 58 | alexaDevServer = new AlexaDevServer(serverless) 59 | expect(Object.keys(alexaDevServer.hooks).length).to.not.equal(0) 60 | }) 61 | 62 | it('should start a server and accept various requests', () => { 63 | serverless.service.functions = { 64 | 'MyAlexaSkill': { 65 | handler: 'lambda-handler.alexaSkill', 66 | events: [ 'alexaSkill' ] 67 | }, 68 | 'MyHttpResource': { 69 | handler: 'lambda-handler.httpGet', 70 | events: [ { http: { method: 'GET', path: '/foo' } } ] 71 | }, 72 | 'MyHttpResourceID': { 73 | handler: 'lambda-handler.httpGet', 74 | events: [ { http: { method: 'GET', path: '/foo/{id}' } } ] 75 | }, 76 | 'MyShorthandHttpResource': { 77 | handler: 'lambda-handler.httpPost', 78 | events: [ { http: 'POST shorthand' } ] 79 | } 80 | } 81 | alexaDevServer = new AlexaDevServer(serverless) 82 | alexaDevServer.hooks['local-dev-server:loadEnvVars']() 83 | alexaDevServer.hooks['local-dev-server:start']() 84 | return Promise.all([ 85 | sendAlexaRequest(5005, 'MyAlexaSkill').then(result => 86 | expect(result.ok).equal(true) 87 | ), 88 | sendHttpGetRequest(5005, 'foo?a=b&c=d').then(result => { 89 | expect(result.status).equal(200) 90 | return result.json().then(json => { 91 | expect(json.queryStringParameters.a).equal('b') 92 | expect(json.queryStringParameters.c).equal('d') 93 | expect(json.pathParameters).eql({}) 94 | expect(json.path).eql('/foo') 95 | expect(json.resource).eql('/foo') 96 | }) 97 | }), 98 | sendHttpGetRequest(5005, 'foo/12345').then(result => { 99 | expect(result.status).equal(200) 100 | return result.json().then(json => { 101 | expect(json.pathParameters.id).eql('12345') 102 | expect(json.resource).eql('/foo/{id}') 103 | expect(json.path).eql('/foo/12345') 104 | }) 105 | }), 106 | sendHttpPostRequest(5005, 'shorthand', {}).then(result => { 107 | expect(result.status).equal(204) 108 | }) 109 | ]) 110 | }) 111 | 112 | it('should start a server with a custom port and accept requests', () => { 113 | serverless.service.functions = { 114 | 'MyHttpResource': { 115 | handler: 'lambda-handler.httpGet', 116 | events: [ { http: 'GET /' } ] 117 | } 118 | } 119 | alexaDevServer = new AlexaDevServer(serverless, { port: 5006 }) 120 | alexaDevServer.hooks['local-dev-server:loadEnvVars']() 121 | alexaDevServer.hooks['local-dev-server:start']() 122 | return sendHttpGetRequest(5006, '').then(result => 123 | expect(result.ok).equal(true) 124 | ) 125 | }) 126 | 127 | it('should set environment variables correctly', () => { 128 | serverless.service.provider.environment = { 129 | foo: 'bar', 130 | bla: 'blub', 131 | la: 'la' 132 | } 133 | serverless.service.functions = { 134 | 'MyAlexaSkill': { 135 | handler: 'lambda-handler.mirrorEnv', 136 | events: [ 'alexaSkill' ], 137 | environment: { 138 | foo: 'baz' 139 | } 140 | } 141 | } 142 | let options = { 143 | environment: { la: 'lala' }, 144 | port: 5007 145 | } 146 | alexaDevServer = new AlexaDevServer(serverless, options) 147 | alexaDevServer.hooks['local-dev-server:loadEnvVars']() 148 | alexaDevServer.hooks['local-dev-server:start']() 149 | return sendAlexaRequest(5007, 'MyAlexaSkill').then(result => { 150 | expect(result.ok).equal(true) 151 | return result.json() 152 | }).then(json => { 153 | expect(json.IS_LOCAL).equal(true) 154 | expect(json.foo).equal('baz') 155 | expect(json.bla).equal('blub') 156 | expect(json.la).equal('lala') 157 | }) 158 | }) 159 | 160 | it('should not start a server if no supported events are specified', () => { 161 | serverless.service.functions = { 162 | 'SomeFunction': { 163 | handler: 'lambda-handler.none', 164 | events: [ 'blub' ] 165 | } 166 | } 167 | alexaDevServer = new AlexaDevServer(serverless, { port: 5008 }) 168 | alexaDevServer.hooks['local-dev-server:loadEnvVars']() 169 | alexaDevServer.hooks['local-dev-server:start']() 170 | // Expect rejection of request as no server is running on port 5008 171 | return expect(sendAlexaRequest(5008)).to.be.rejected 172 | }) 173 | 174 | it('should handle failures', () => { 175 | serverless.service.functions = { 176 | 'MyAlexaSkill': { 177 | handler: 'lambda-handler.fail', 178 | events: [ 'alexaSkill' ] 179 | }, 180 | 'MyHttpResource': { 181 | handler: 'lambda-handler.fail', 182 | events: [ { http: 'GET /' } ] 183 | } 184 | } 185 | alexaDevServer = new AlexaDevServer(serverless, { port: 5009 }) 186 | alexaDevServer.hooks['local-dev-server:loadEnvVars']() 187 | alexaDevServer.hooks['local-dev-server:start']() 188 | return Promise.all([ 189 | sendAlexaRequest(5009).then(result => 190 | expect(result.ok).equal(false) 191 | ), 192 | sendHttpGetRequest(5009, '').then(result => 193 | expect(result.ok).equal(false) 194 | ) 195 | ]) 196 | }) 197 | }) 198 | -------------------------------------------------------------------------------- /test/lambda-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Invokes the succeed callback 4 | module.exports.succeed = (_, context) => { 5 | context.succeed() 6 | } 7 | 8 | // Invokes the fail callback 9 | module.exports.fail = (_, context) => { 10 | context.fail(new Error('Some reason')) 11 | } 12 | 13 | // Returns process.env 14 | module.exports.mirrorEnv = (request, context) => { 15 | context.succeed(process.env) 16 | } 17 | 18 | // Succeed if request object has correct form 19 | module.exports.alexaSkill = (request, context) => { 20 | if (!request.session) { 21 | context.fail(new Error('session-object not in request JSON')) 22 | } else if (!request.request) { 23 | context.fail(new Error('request-object not in request JSON')) 24 | } else if (request.version !== '1.0') { 25 | context.fail(new Error('version not 1.0')) 26 | } else { 27 | context.succeed() 28 | } 29 | } 30 | 31 | // Succeed if request object has correct form, returning the request object 32 | module.exports.httpGet = (request, context) => { 33 | if (request.httpMethod !== 'GET') { 34 | context.fail(new Error('httpMethod should be GET')) 35 | } else if (request.body.toString() !== '{}') { 36 | context.fail(new Error('body should be empty')) 37 | } else { 38 | context.succeed({ 39 | headers: { 'Content-Type': 'application/json' }, 40 | statusCode: 200, 41 | body: request 42 | }) 43 | } 44 | } 45 | 46 | // Succeed if request object has correct form 47 | module.exports.httpPost = (request, context) => { 48 | if (request.httpMethod !== 'POST') { 49 | context.fail(new Error('httpMethod not POST')) 50 | } else if (request.body.toString() === '{"foo":"bar"}') { 51 | context.fail(new Error('body should not be empty')) 52 | } else { 53 | context.succeed({ 54 | statusCode: 204 55 | }) 56 | } 57 | } 58 | --------------------------------------------------------------------------------