Repository: serverless/serverless (JavaScript)
Date: 2018-02-24 17:23:28+00:00
Task type: Bug Fix

Issue:
UsagePlan is not created unless apiKeys are defined
<!--
1. If you have a question and not a bug/feature request please ask it at http://forum.serverless.com
2. Please check if an issue already exists so there are no duplicates
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Bug Report

## Description

For bug reports:
* What went wrong?
UsagePlan not created when apiKeys are not defined
* What did you expect should have happened?
UsagePlan should create if specified in config
* What was the config you used?

service: device-v1

custom: ${file(../custom-properties-${opt:stage, self:provider.stage}.yml)}

provider:
  name: aws
  runtime: nodejs6.10
  region: ${self:custom.region}
  usagePlan:
    throttle:
      burstLimit: 200
      rateLimit: 100

* What stacktrace or error message from your provider did you see?
no stacktrace or error message. Just UsagePlan not created

## Additional Data
I have two use cases for creating UsagePlans without ApiKeys:
1) I have a custom resource and lambda's that manage the creation and deletion of ApiKeys. It is not possible to create a UsagePlan in the Resources section due to the ApiGatewayDeployment random number, so I want serverless to do it, but I currently need to create an an ApiKey to force the UsagePlan to create.
2) I have an api that doesn't use ApiKeys but I want to create a BasePathMapping resource which requires a DependsOn ApiGatewayDeployment, but since there is a random number in that, I can depend on a unused UsagePlan (which does depend on ApiGatewayDeployment). 

* ***Serverless Framework Version you're using***:
1.24.1
* ***Operating System***:
Ubuntu 16.04.3 LTS
* ***Stack Trace***:
N/A
* ***Provider Error messages***:
N/A


Issue discussion:
The change to make this work is trivial, please see attached one liner diff that I'm using.

[usagePlan.js.diff.txt](https://github.com/serverless/serverless/files/1454615/usagePlan.js.diff.txt)

Thank you for reporting and putting the code for resolving it @bsdkurt :+1: 
I have tried your code and great to see working fine :100: 

For further verification, cloud you send pull request with your fix?
Additionally, we need to fix test code related to that as well.

Solution patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js
index 7bc199bb34e..e877b2aed09 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js
@@ -5,7 +5,7 @@ const BbPromise = require('bluebird');
 
 module.exports = {
   compileUsagePlan() {
-    if (this.serverless.service.provider.apiKeys) {
+    if (this.serverless.service.provider.usagePlan || this.serverless.service.provider.apiKeys) {
       this.apiGatewayUsagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId();
       _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
         [this.apiGatewayUsagePlanLogicalId]: {


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js
index 370a8c59471..d43e8fe4746 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js
@@ -17,21 +17,6 @@ describe('#compileUsagePlan()', () => {
     serverless = new Serverless();
     serverless.setProvider('aws', new AwsProvider(serverless, options));
     serverless.service.service = 'first-service';
-    serverless.service.provider = {
-      name: 'aws',
-      apiKeys: ['1234567890'],
-      usagePlan: {
-        quota: {
-          limit: 500,
-          offset: 10,
-          period: 'MONTH',
-        },
-        throttle: {
-          burstLimit: 200,
-          rateLimit: 100,
-        },
-      },
-    };
     serverless.service.provider.compiledCloudFormationTemplate = {
       Resources: {},
       Outputs: {},
@@ -41,8 +26,67 @@ describe('#compileUsagePlan()', () => {
     awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
   });
 
-  it('should compile usage plan resource', () =>
-    awsCompileApigEvents.compileUsagePlan().then(() => {
+  it('should compile default usage plan resource', () => {
+    serverless.service.provider.apiKeys = ['1234567890'];
+    return awsCompileApigEvents.compileUsagePlan().then(() => {
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
+          ].Type
+      ).to.equal('AWS::ApiGateway::UsagePlan');
+
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
+          ].DependsOn
+      ).to.equal('ApiGatewayDeploymentTest');
+
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
+          ].Properties.ApiStages[0].ApiId.Ref
+      ).to.equal('ApiGatewayRestApi');
+
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
+          ].Properties.ApiStages[0].Stage
+      ).to.equal('dev');
+
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
+          ].Properties.Description
+      ).to.equal('Usage plan for first-service dev stage');
+
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
+          .Resources[
+          awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
+          ].Properties.UsagePlanName
+      ).to.equal('first-service-dev');
+    });
+  });
+
+  it('should compile custom usage plan resource', () => {
+    serverless.service.provider.usagePlan = {
+      quota: {
+        limit: 500,
+        offset: 10,
+        period: 'MONTH',
+      },
+      throttle: {
+        burstLimit: 200,
+        rateLimit: 100,
+      },
+    };
+
+    return awsCompileApigEvents.compileUsagePlan().then(() => {
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
           .Resources[
@@ -105,6 +149,6 @@ describe('#compileUsagePlan()', () => {
           awsCompileApigEvents.provider.naming.getUsagePlanLogicalId()
           ].Properties.UsagePlanName
       ).to.equal('first-service-dev');
-    })
-  );
+    });
+  });
 });