Repository: HyperBrain/serverless-aws-alias
Pull request #59 (2017-06-22 12:41:44)

Stage configuration
Implements #57 

Full APIG configuration is now available on service, function and endpoint levels.

coveralls: 
[![Coverage Status](https://:/builds/12084939/badge)](https://:/builds/12084939)

Coverage decreased (-1.6%) to 58.467% when pulling **19340fa4426485ba9ae078e7dbe80c0f394aafd3 on stage-configuration** into **7cc0d82c31646e8871f372376bf45e67bf7f7267 on master**.

coveralls: 
[![Coverage Status](https://:/builds/12084960/badge)](https://:/builds/12084960)

Coverage decreased (-1.6%) to 58.467% when pulling **19340fa4426485ba9ae078e7dbe80c0f394aafd3 on stage-configuration** into **7cc0d82c31646e8871f372376bf45e67bf7f7267 on master**.

coveralls: 
[![Coverage Status](https://:/builds/12086479/badge)](https://:/builds/12086479)

Coverage increased (+2.2%) to 62.323% when pulling **4eff353779f18d2314b490baabb4b8ef171538d3 on stage-configuration** into **7cc0d82c31646e8871f372376bf45e67bf7f7267 on master**.

coveralls: 
[![Coverage Status](https://:/builds/12086876/badge)](https://:/builds/12086876)

Coverage increased (+2.2%) to 62.323% when pulling **74f305ab5b2e5be21e648dcdb8221042deaccd15 on stage-configuration** into **7cc0d82c31646e8871f372376bf45e67bf7f7267 on master**.


Patch:
diff --git a/README.md b/README.md
index cba17ea..447fed4 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,89 @@ Lambda invocation. This will call the aliased function version.
 Deployed stages have the alias stage variable set fixed, so a deployed alias stage is
 hard-wired to the aliased Lambda versions.
 
+### Stage configuration (NEW)
+
+The alias plugin supports configuring the deployed API Gateway stages, exactly as
+you can do it within the AWS APIG console, e.g. you can configure logging (with
+or without data/request tracing), setup caching or throttling on your endpoints.
+
+The configuration can be done on a service wide level, function level or method level
+by adding an `aliasStage` object either to `provider`, `any function` or a `http event`
+within a function in your _serverless.yml_. The configuration is applied hierarchically,
+where the inner configurations overwrite the outer ones.
+
+`HTTP Event -> FUNCTION -> SERVICE`
+
+#### The aliasStage configuration object
+
+All settings are optional, and if not specified will be set to the AWS stage defaults.
+
+```
+aliasStage:
+  cacheDataEncrypted: (Boolean)
+  cacheTtlInSeconds: (Integer)
+  cachingEnabled: (Boolean)
+  dataTraceEnabled: (Boolean) - Log full request/response bodies
+  loggingLevel: ("OFF", "INFO" or "ERROR")
+  metricsEnabled: (Boolean) - Enable detailed CW metrics
+  throttlingBurstLimit: (Integer)
+  throttlingRateLimit: (Number)
+```
+
+There are two further options that can only be specified on a service level and that
+affect the whole stage:
+
+```
+aliasStage:
+  cacheClusterEnabled: (Boolean)
+  cacheClusterSize: (Integer)
+```
+
+For more information see the [AWS::APIGateway::Stage](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html) or [MethodSettings](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-stage-methodsetting.html) documentation
+on the AWS website.
+
+Sample serverless.yml (partial):
+
+```
+service: sls-test-project
+
+provider:
+  ...
+  # Enable detailed error logging on all endpoints
+  aliasStage:
+    loggingLevel: "ERROR"
+    dataTraceEnabled: true
+  ...
+
+functions:
+  myFunc1:
+    ...
+    # myFunc1 should generally not log anything
+    aliasStage:
+      loggingLevel: "OFF"
+      dataTraceEnabled: false
+    events:
+      - http:
+          method: GET
+          path: /func1
+      - http:
+          method: POST
+          path: /func1/create
+      - http:
+          method: PATCH
+          path: /func1/update
+          # The update endpoint needs special settings
+          aliasStage:
+            loggingLevel: "INFO"
+            dataTraceEnabled: true
+            throttlingBurstLimit: 200
+            throttlingRateLimit: 100
+
+  myFunc2:
+    ...
+    # Will inherit the global settings if nothing is set on function level
+```
+
 ## Reference the current alias in your service
 
 You can reference the currently deployed alias with `${self:provider.alias}` in
@@ -174,11 +257,11 @@ functions:
           path: /func1
 resources:
   Resources:
-	  myKinesis:
-		  Type: AWS::Kinesis::Stream
-			Properties:
-			  Name: my-kinesis
-			  ShardCount: 1
+    myKinesis:
+      Type: AWS::Kinesis::Stream
+      Properties:
+        Name: my-kinesis
+        ShardCount: 1
 ```
 
 When a function is deployed to an alias it will now also listen to the *my-kinesis*
@@ -214,11 +297,11 @@ functions:
           path: /func1
 resources:
   Resources:
-	  myKinesis${self:provider.alias}:
-		  Type: AWS::Kinesis::Stream
-			Properties:
-			  Name: my-kinesis-${self.provider.alias}
-			  ShardCount: 1
+    myKinesis${self:provider.alias}:
+      Type: AWS::Kinesis::Stream
+      Properties:
+        Name: my-kinesis-${self.provider.alias}
+        ShardCount: 1
 ```
 
 ### Named streams
@@ -343,7 +426,7 @@ The plugin adds the following lifecycle events that can be hooked by other plugi
 * alias:deploy:done
 
   The Alias plugin is successfully finished. Hook this instead of 'after:deploy:deploy'
-	to make sure that your plugin gets triggered right after the alias plugin is done.
+  to make sure that your plugin gets triggered right after the alias plugin is done.
 
 * alias:remove:removeStack
 
@@ -360,8 +443,8 @@ and _serverless.service.provider.deployedAliasTemplates[]_.
 
 * The master alias for a stage could be protected by a separate stack policy that
   only allows admin users to deploy or change it. The stage stack does not have
-	to be protected individually because the stack cross references prohibit changes
-	naturally. It might be possible to introduce some kind of per alias policy.
+  to be protected individually because the stack cross references prohibit changes
+  naturally. It might be possible to introduce some kind of per alias policy.
 
 ## Version history
 
@@ -381,19 +464,3 @@ and _serverless.service.provider.deployedAliasTemplates[]_.
 
 
 * 1.0.0        Support "serverless logs" with aliases. First non-alpha!
-* 0.5.1-alpha1 Use separate Lambda roles per alias
-* 0.5.0-alpha1 Fixes a bug with deploying event sources introduced with 0.4.0
-               Use new event model introduced in SLS 1.12. Needs SLS 1.12 or greater from now on.
-							 Add support for CW events.
-							 Set SERVERLESS_ALIAS environment variable on deployed functions.
-* 0.4.0-alpha1 APIG support fixed. Support external IAM roles. BREAKING.
-* 0.3.4-alpha1 Bugfixes. IAM policy consolitaion. Show master alias information.
-* 0.3.3-alpha1 Bugfixes. Allow manual resource overrides. Allow methods attached to APIG root resource.
-* 0.3.2-alpha1 Allow initial project creation with activated alias plugin
-* 0.3.1-alpha1 Support Serverless 1.6 again with upgrade to 1.7+
-* 0.3.0-alpha1 Support lambda event subscriptions
-* 0.2.1-alpha1 Alias remove command removes unused resources
-* 0.2.0-alpha1 Support custom resources
-* 0.1.2-alpha1 Integration with "serverless info"
-* 0.1.1-alpha1 Full APIG support
-* 0.1.0-alpha1 Lambda function alias support
diff --git a/lib/stackops/apiGateway.js b/lib/stackops/apiGateway.js
index 0dd270d..181d5ad 100644
--- a/lib/stackops/apiGateway.js
+++ b/lib/stackops/apiGateway.js
@@ -10,6 +10,103 @@ const _ = require('lodash');
 const BbPromise = require('bluebird');
 const utils = require('../utils');
 
+const stageMethodConfigMappings = {
+	cacheDataEncrypted: { prop: 'CacheDataEncrypted', validate: _.isBoolean, default: false },
+	cacheTtlInSeconds: { prop: 'CacheTtlInSeconds', validate: _.isInteger },
+	cachingEnabled: { prop: 'CachingEnabled', validate: _.isBoolean, default: false },
+	dataTraceEnabled: { prop: 'DataTraceEnabled', validate: _.isBoolean, default: false },
+	loggingLevel: { prop: 'LoggingLevel', validate: value => _.includes([ 'OFF', 'INFO', 'ERROR' ], value), default: 'OFF' },
+	metricsEnabled: { prop: 'MetricsEnabled', validate: _.isBoolean, default: false },
+	throttlingBurstLimit: { prop: 'ThrottlingBurstLimit', validate: _.isInteger },
+	throttlingRateLimit: { prop: 'ThrottlingRateLimit', validate: _.isNumber }
+};
+
+/**
+ * Namespace for APIG processing internal functions
+ */
+const internal = {
+	/**
+	 * Creates a stage resource and configures it depending on the project settings.
+	 * @this The current instance of the alias plugin
+	 * @param restApiRef {String} Stack reference to rest API id
+	 * @param deploymentName {String} Current deployment.
+	 * @returns {Object} - AWS::ApiGateway::Stage
+	 */
+	createStageResource(restApiRef, deploymentName) {
+		// Create stage resource
+		const stageResource = {
+			Type: 'AWS::ApiGateway::Stage',
+			Properties: {
+				StageName: this._alias,
+				DeploymentId: {
+					Ref: deploymentName
+				},
+				RestApiId: {
+					'Fn::ImportValue': restApiRef
+				},
+				Variables: {
+					SERVERLESS_ALIAS: this._alias,
+					SERVERLESS_STAGE: this._stage
+				}
+			},
+			DependsOn: [ deploymentName ]
+		};
+
+		// Set a reasonable description
+		const serviceName = _.get(this.serverless.service.getServiceObject() || {}, 'name');
+		stageResource.Properties.Description = `Alias stage '${this._alias}' for ${serviceName}`;
+
+		// Configure stage (service level)
+		const serviceLevelConfig = _.cloneDeep(_.get(this.serverless.service, 'provider.aliasStage', {}));
+		if (serviceLevelConfig.cacheClusterEnabled === true) {
+			stageResource.Properties.CacheClusterEnabled = true;
+			if (_.has(serviceLevelConfig, 'cacheClusterSize')) {
+				stageResource.Properties.CacheClusterSize = serviceLevelConfig.cacheClusterSize;
+			}
+		}
+		delete serviceLevelConfig.cacheClusterEnabled;
+		delete serviceLevelConfig.cacheClusterSize;
+
+		// Configure methods/functions
+		const methodSettings = [];
+		const functions = this.serverless.service.getAllFunctions();
+		_.forEach(functions, funcName => {
+			const func = this.serverless.service.getFunction(funcName);
+			const funcStageConfig = _.defaults({}, func.aliasStage, serviceLevelConfig);
+			const funcHttpEvents = _.compact(_.map(this.serverless.service.getAllEventsInFunction(funcName), event => event.http));
+
+			_.forEach(funcHttpEvents, httpEvent => {
+				const eventStageConfig = _.defaults({}, httpEvent.aliasStage, funcStageConfig);
+				if (!_.isEmpty(eventStageConfig)) {
+					const methodType = _.toUpper(httpEvent.method);
+					const methodSetting = {};
+					_.forOwn(eventStageConfig, (value, key) => {
+						if (!_.has(stageMethodConfigMappings, key)) {
+							throw new this.serverless.classes.Error(`Invalid stage config '${key}' at method '${methodType} /${httpEvent.path}'`);
+						} else if (!stageMethodConfigMappings[key].validate(value)) {
+							throw new this.serverless.classes.Error(`Invalid value for stage config '${key}: ${value}' at method '${methodType} /${httpEvent.path}'`);
+						}
+						if (!_.has(stageMethodConfigMappings[key], 'default') || stageMethodConfigMappings[key].default !== value) {
+							methodSetting[stageMethodConfigMappings[key].prop] = value;
+						}
+					});
+					if (!_.isEmpty(methodSetting)) {
+						methodSetting.HttpMethod = methodType;
+						methodSetting.ResourcePath = '/' + _.replace('/' + _.trimStart(httpEvent.path, '/'), /\//g, '~1');
+						methodSettings.push(methodSetting);
+					}
+				}
+			});
+		});
+
+		if (!_.isEmpty(methodSettings)) {
+			stageResource.Properties.MethodSettings = methodSettings;
+		}
+
+		return stageResource;
+	}
+};
+
 module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
 	const stackName = this._provider.naming.getStackName();
 	const stageStack = this._serverless.service.provider.compiledCloudFormationTemplate;
@@ -74,25 +171,9 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
 			delete stageStack.Resources[deploymentName];
 
 			// Create stage resource
-			const stageResource = {
-				Type: 'AWS::ApiGateway::Stage',
-				Properties: {
-					StageName: this._alias,
-					DeploymentId: {
-						Ref: deploymentName
-					},
-					RestApiId: {
-						'Fn::ImportValue': `${stackName}-ApiGatewayRestApi`
-					},
-					Variables: {
-						SERVERLESS_ALIAS: this._alias,
-						SERVERLESS_STAGE: this._stage
-					}
-				},
-				DependsOn: [ deploymentName ]
-			};
+			this.options.verbose && this._serverless.cli.log('Configuring stage');
+			const stageResource = internal.createStageResource.call(this, `${stackName}-ApiGatewayRestApi`, deploymentName);
 			aliasResources.push({ ApiGatewayStage: stageResource });
-
 		}
 
 		// Fetch lambda permissions, methods and resources. These have to be updated later to allow the aliased functions.
@@ -207,3 +288,6 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
 
 	return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
 };
+
+// Exports to make internal functions available for unit tests
+module.exports.internal = internal;


Test patch:
diff --git a/test/stackops/apiGateway.test.js b/test/stackops/apiGateway.test.js
index 071bdaa..4e5a859 100644
--- a/test/stackops/apiGateway.test.js
+++ b/test/stackops/apiGateway.test.js
@@ -15,6 +15,7 @@ const AwsProvider = require(`${serverlessPath}/lib/plugins/aws/provider/awsProvi
 const Serverless = require(`${serverlessPath}/lib/Serverless`);
 
 chai.use(require('chai-as-promised'));
+chai.use(require('chai-subset'));
 chai.use(require('sinon-chai'));
 const expect = chai.expect;
 
@@ -55,6 +56,509 @@ describe('API Gateway', () => {
 		sandbox.restore();
 	});
 
+	describe('#createStageResource()', () => {
+		let createStageResource;
+
+		beforeEach(() => {
+			createStageResource = _.bind(require('../../lib/stackops/apiGateway').internal.createStageResource, awsAlias);
+		});
+
+		it('should not throw with simple service', () => {
+			expect(() => createStageResource('apiRef', 'deployment')).to.not.throw;
+		});
+
+		it('should return a valid stage object', () => {
+			const stage = createStageResource('apiRef', 'deployment');
+
+			expect(stage).to.have.a.property('Type', 'AWS::ApiGateway::Stage');
+			expect(stage).to.have.a.deep.property('Properties.StageName', 'myAlias');
+			expect(stage).to.have.a.deep.property('Properties.DeploymentId').that.is.an('object').that.deep.equals({ Ref: 'deployment' });
+			expect(stage).to.have.a.deep.property('Properties.RestApiId').that.is.an('object').that.deep.equals({ 'Fn::ImportValue': 'apiRef' });
+			expect(stage).to.have.a.deep.property('Properties.Variables').that.is.an('object').that.containSubset({ SERVERLESS_ALIAS: 'myAlias' });
+		});
+
+		it('should set general stage configuration', () => {
+			awsAlias.serverless.service.provider.aliasStage = {
+				cacheClusterEnabled: true,
+				cacheClusterSize: 2
+			};
+
+			const stage = createStageResource('apiRef', 'deployment');
+			expect(stage).to.have.a.deep.property('Properties.CacheClusterEnabled', true);
+			expect(stage).to.have.a.deep.property('Properties.CacheClusterSize', 2);
+		});
+
+		it('should omit cacheClusterSize if not given', () => {
+			awsAlias.serverless.service.provider.aliasStage = {
+				cacheClusterEnabled: true
+			};
+
+			const stage = createStageResource('apiRef', 'deployment');
+			expect(stage).to.not.have.a.deep.property('Properties.CacheClusterSize');
+		});
+
+		it('should throw on invalid configuration keys', () => {
+			const service = {
+				service: 'testService',
+				serviceObject: {
+					name: 'testService'
+				},
+				provider: {
+					name: 'aws',
+					runtime: 'nodejs4.3',
+					stage: 'myStage',
+					alias: 'myAlias',
+					region: 'us-east-1',
+					aliasStage: {
+						notSomethingUnderstood: 'INFO',
+						cacheClusterSize: 2,
+						metricsEnabled: true
+					}
+				},
+				functions: {
+					functionA: {
+						handler: 'functionA.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcA'
+								}
+							},
+						],
+					}
+				}
+			};
+
+			awsAlias.serverless.service = new awsAlias.serverless.classes.Service(awsAlias.serverless, service);
+
+			expect(() => createStageResource('apiRef', 'deployment')).to.throw('Invalid stage config');
+		});
+
+		it('should throw on invalid configuration values', () => {
+			const service = {
+				service: 'testService',
+				serviceObject: {
+					name: 'testService'
+				},
+				provider: {
+					name: 'aws',
+					runtime: 'nodejs4.3',
+					stage: 'myStage',
+					alias: 'myAlias',
+					region: 'us-east-1',
+					aliasStage: {
+						cacheClusterSize: 2,
+						metricsEnabled: 'true'
+					}
+				},
+				functions: {
+					functionA: {
+						handler: 'functionA.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcA'
+								}
+							},
+						],
+					}
+				}
+			};
+
+			awsAlias.serverless.service = new awsAlias.serverless.classes.Service(awsAlias.serverless, service);
+
+			expect(() => createStageResource('apiRef', 'deployment')).to.throw('Invalid value for');
+		});
+
+		it('should use service config', () => {
+			const service = {
+				service: 'testService',
+				serviceObject: {
+					name: 'testService'
+				},
+				provider: {
+					name: 'aws',
+					runtime: 'nodejs4.3',
+					stage: 'myStage',
+					alias: 'myAlias',
+					region: 'us-east-1',
+					aliasStage: {
+						loggingLevel: 'INFO'
+					}
+				},
+				functions: {
+					functionA: {
+						handler: 'functionA.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcA'
+								}
+							},
+							{
+								http: {
+									method: 'POST',
+									path: '/funcA/create'
+								}
+							}
+						]
+					},
+					functionB: {
+						handler: 'functionB.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcB'
+								}
+							},
+							{
+								http: {
+									method: 'UPDATE',
+									path: '/funcB/update'
+								}
+							}
+						]
+					},
+				}
+			};
+			const expectedMethodSettings = [
+				{
+					LoggingLevel: 'INFO',
+					HttpMethod: 'GET',
+					ResourcePath: '/~1funcA'
+				},
+				{
+					LoggingLevel: 'INFO',
+					HttpMethod: 'POST',
+					ResourcePath: '/~1funcA~1create'
+				},
+				{
+					LoggingLevel: 'INFO',
+					HttpMethod: 'GET',
+					ResourcePath: '/~1funcB'
+				},
+				{
+					LoggingLevel: 'INFO',
+					HttpMethod: 'UPDATE',
+					ResourcePath: '/~1funcB~1update'
+				},
+			];
+
+			awsAlias.serverless.service = new awsAlias.serverless.classes.Service(awsAlias.serverless, service);
+
+			const stage = createStageResource('apiRef', 'deployment');
+			expect(stage).to.have.a.deep.property('Properties.MethodSettings').that.deep.equals(expectedMethodSettings);
+		});
+
+		it('should prefer function config', () => {
+			const service = {
+				service: 'testService',
+				serviceObject: {
+					name: 'testService'
+				},
+				provider: {
+					name: 'aws',
+					runtime: 'nodejs4.3',
+					stage: 'myStage',
+					alias: 'myAlias',
+					region: 'us-east-1',
+					aliasStage: {
+						loggingLevel: 'INFO'
+					}
+				},
+				functions: {
+					functionA: {
+						handler: 'functionA.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcA'
+								}
+							},
+							{
+								http: {
+									method: 'POST',
+									path: '/funcA/create'
+								}
+							}
+						]
+					},
+					functionB: {
+						handler: 'functionB.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcB'
+								}
+							},
+							{
+								http: {
+									method: 'UPDATE',
+									path: '/funcB/update'
+								}
+							}
+						],
+						aliasStage: {
+							loggingLevel: 'ERROR',
+							metricsEnabled: true
+						}
+					},
+				}
+			};
+			const expectedMethodSettings = [
+				{
+					LoggingLevel: 'INFO',
+					HttpMethod: 'GET',
+					ResourcePath: '/~1funcA'
+				},
+				{
+					LoggingLevel: 'INFO',
+					HttpMethod: 'POST',
+					ResourcePath: '/~1funcA~1create'
+				},
+				{
+					LoggingLevel: 'ERROR',
+					MetricsEnabled: true,
+					HttpMethod: 'GET',
+					ResourcePath: '/~1funcB'
+				},
+				{
+					LoggingLevel: 'ERROR',
+					MetricsEnabled: true,
+					HttpMethod: 'UPDATE',
+					ResourcePath: '/~1funcB~1update'
+				},
+			];
+
+			awsAlias.serverless.service = new awsAlias.serverless.classes.Service(awsAlias.serverless, service);
+
+			const stage = createStageResource('apiRef', 'deployment');
+			expect(stage).to.have.a.deep.property('Properties.MethodSettings').that.deep.equals(expectedMethodSettings);
+		});
+
+		it('should prefer event config', () => {
+			const service = {
+				service: 'testService',
+				serviceObject: {
+					name: 'testService'
+				},
+				provider: {
+					name: 'aws',
+					runtime: 'nodejs4.3',
+					stage: 'myStage',
+					alias: 'myAlias',
+					region: 'us-east-1',
+					aliasStage: {
+						loggingLevel: 'INFO'
+					}
+				},
+				functions: {
+					functionA: {
+						handler: 'functionA.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcA'
+								}
+							},
+							{
+								http: {
+									method: 'POST',
+									path: '/funcA/create',
+									aliasStage: {
+										metricsEnabled: true
+									}
+								}
+							}
+						]
+					},
+					functionB: {
+						handler: 'functionB.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcB'
+								}
+							},
+							{
+								http: {
+									method: 'UPDATE',
+									path: '/funcB/update',
+									aliasStage: {
+										loggingLevel: 'INFO',
+										cachingEnabled: true,
+									}
+								}
+							}
+						],
+						aliasStage: {
+							loggingLevel: 'ERROR',
+							metricsEnabled: true,
+						}
+					},
+				}
+			};
+			const expectedMethodSettings = [
+				{
+					LoggingLevel: 'INFO',
+					HttpMethod: 'GET',
+					ResourcePath: '/~1funcA'
+				},
+				{
+					LoggingLevel: 'INFO',
+					MetricsEnabled: true,
+					HttpMethod: 'POST',
+					ResourcePath: '/~1funcA~1create'
+				},
+				{
+					LoggingLevel: 'ERROR',
+					MetricsEnabled: true,
+					HttpMethod: 'GET',
+					ResourcePath: '/~1funcB'
+				},
+				{
+					LoggingLevel: 'INFO',
+					MetricsEnabled: true,
+					CachingEnabled: true,
+					HttpMethod: 'UPDATE',
+					ResourcePath: '/~1funcB~1update'
+				},
+			];
+
+			awsAlias.serverless.service = new awsAlias.serverless.classes.Service(awsAlias.serverless, service);
+
+			const stage = createStageResource('apiRef', 'deployment');
+			expect(stage).to.have.a.deep.property('Properties.MethodSettings').that.deep.equals(expectedMethodSettings);
+		});
+
+		it('should not set AWS default values', () => {
+			const service = {
+				service: 'testService',
+				serviceObject: {
+					name: 'testService'
+				},
+				provider: {
+					name: 'aws',
+					runtime: 'nodejs4.3',
+					stage: 'myStage',
+					alias: 'myAlias',
+					region: 'us-east-1',
+					aliasStage: {
+						loggingLevel: 'INFO'
+					}
+				},
+				functions: {
+					functionB: {
+						handler: 'functionB.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcB'
+								}
+							},
+							{
+								http: {
+									method: 'UPDATE',
+									path: '/funcB/update',
+									aliasStage: {
+										loggingLevel: 'ERROR',
+									}
+								}
+							},
+							{
+								http: {
+									method: 'PATCH',
+									path: '/funcB/update',
+									aliasStage: {
+										loggingLevel: 'OFF',
+										metricsEnabled: false
+									}
+								}
+							}
+						],
+						aliasStage: {
+							loggingLevel: 'OFF',
+							metricsEnabled: true
+						}
+					},
+				}
+			};
+			const expectedMethodSettings = [
+				{
+					MetricsEnabled: true,
+					HttpMethod: 'GET',
+					ResourcePath: '/~1funcB'
+				},
+				{
+					LoggingLevel: 'ERROR',
+					MetricsEnabled: true,
+					HttpMethod: 'UPDATE',
+					ResourcePath: '/~1funcB~1update'
+				},
+			];
+
+			awsAlias.serverless.service = new awsAlias.serverless.classes.Service(awsAlias.serverless, service);
+
+			const stage = createStageResource('apiRef', 'deployment');
+			expect(stage).to.have.a.deep.property('Properties.MethodSettings').that.deep.equals(expectedMethodSettings);
+		});
+
+		it('should not set stage config without actual configuration', () => {
+			const service = {
+				service: 'testService',
+				serviceObject: {
+					name: 'testService'
+				},
+				provider: {
+					name: 'aws',
+					runtime: 'nodejs4.3',
+					stage: 'myStage',
+					alias: 'myAlias',
+					region: 'us-east-1',
+				},
+				functions: {
+					functionB: {
+						handler: 'functionB.handler',
+						events: [
+							{
+								http: {
+									method: 'GET',
+									path: '/funcB'
+								}
+							},
+							{
+								http: {
+									method: 'UPDATE',
+									path: '/funcB/update',
+								}
+							},
+							{
+								http: {
+									method: 'PATCH',
+									path: '/funcB/update',
+								}
+							}
+						],
+					},
+				}
+			};
+
+			awsAlias.serverless.service = new awsAlias.serverless.classes.Service(awsAlias.serverless, service);
+
+			const stage = createStageResource('apiRef', 'deployment');
+			expect(stage).to.not.have.a.deep.property('Properties.MethodSettings');
+		});
+	});
+
 	describe('#aliasHandleApiGateway()', () => {
 		it('should succeed with standard template', () => {
 			serverless.service.provider.compiledCloudFormationTemplate = require('../data/sls-stack-1.json');